-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcontent.json
1 lines (1 loc) · 609 KB
/
content.json
1
{"meta":{"title":"Xu's Blog","subtitle":"博观而约取,厚积而薄发","description":"终有溺水替沧海,再无相思寄巫山~","author":"·银河小徐","url":"https://blog.hasaik.com","root":"/"},"pages":[{"title":"","date":"2021-03-25T13:28:10.065Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"404.html","permalink":"https://blog.hasaik.com/404.html","excerpt":"","text":"404 很抱歉,您访问的页面不存在 可能是输入地址有误或该地址已被删除"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"about/index.html","permalink":"https://blog.hasaik.com/about/index.html","excerpt":"","text":"🍂关于我 真(ま)白(しろ) 对话中... 赏 蟹蟹大噶们 ~ (^__^) Y…… 赞助人 金额 时间 渠道 留言 1988 ¥ 1.00 2020-11-12 微信 你的博客太强了,自己也想做一下,学习一下 咸*g ¥ 2.00 2020-09-04 微信 博主的about me有意思 YourBatman ¥ 3.33 2020-05-08 微信 喜欢的风格,支持一下 *生 ¥ 8.88 2020-04-06 微信 blog很酷!谢谢! 小包小白 ¥ 0.96 2019-10-12 微信 Ricardo Li ¥ 6.66 2019-11-27 支付宝 玩家 ¥ 0.01 2019-11-28 微信 宝贝 ¥ 5.21 2020-01-06 支付宝 有什么话要对我说吗?这里是你畅所欲言的地方,可以咨询,可以交流,可以感叹,可以发飙,但 不!可!以!订外卖 .single-reward { position: relative; width: 100%; margin: 30px auto; text-align: center; z-index: 999 } .single-reward .reward-open { position: relative; line-height: 22px; width: 35px; height: 35px; padding: 7px; color: #fff; text-align: center; display: inline-block; border-radius: 100%; background: #d34836; cursor: url(https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/ayuda.cur), auto; } .single-reward .reward-main { position: absolute; top: 45px; left: -156px; margin: 0; padding: 4px 0 0; width: 355px; background: 0 0; display: none; animation: main .4s } .reward-open:hover .reward-main { display: block } .single-reward .reward-row { margin: 0 auto; padding: 20px 15px 10px; background: #f5f5f5; display: inline-block; border-radius: 4px; } .single-reward .reward-row:before { content: \"\"; width: 0; height: 0; border-left: 13px solid transparent; border-right: 13px solid transparent; border-bottom: 13px solid #f5f5f5; position: absolute; top: -9px; left: -9px; right: 0; margin: 0 auto } .single-reward .reward-row li { list-style-type: none; padding: 0 12px; display: inline-block } .reward-row li img { width: 130px; max-width: 130px; border-radius: 3px; position: relative } .reward-row li::after { margin-top: 10px; display: block; font-size: 13px; color: #121212; } .alipay-code:after { content: \"支付宝\" } .wechat-code:after { content: \"微信\" } .md .single-reward ul li:before{ content: none }"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"catchTheCat/index.html","permalink":"https://blog.hasaik.com/catchTheCat/index.html","excerpt":"","text":"游戏规则: 🔔 点击小圆点,围住小猫。 🔔 你点击一次,小猫走一次。 🔔 直到你把小猫围住(赢),或者小猫走到边界并逃跑(输)。"},{"title":"所有分类","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"categories/index.html","permalink":"https://blog.hasaik.com/categories/index.html","excerpt":"","text":""},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"comments/index.html","permalink":"https://blog.hasaik.com/comments/index.html","excerpt":"","text":"🍭留言板 一言 挑选中... 有什么想说的,有什么想问,就在下方留言吧,收到我会第一时间回复!请尽情灌水吧!😉 $.get(\"https://v1.hitokoto.cn?c=i&c=j\", function (data, status) { if (status == 'success') { $('#poem').html(data.hitokoto); if (data.from_who != null) { $('#info').html(data.from_who + \" · \" + \"《 \" + data.from + \" 》\"); } else { $('#info').html(data.from); } } else { $('#poem').html(\"获取出错啦\"); } }); .poem-wrap { position: relative; width: 730px; max-width: 80%; border: 2px solid #797979; border-top: 0; text-align: center; margin: 80px auto; } .poem-wrap h1 { position: relative; margin-top: -20px; display: inline-block; letter-spacing: 4px; color: #797979; border-bottom: none; } .md .poem-wrap h1:before{ height: 0; } .poem-wrap p { width: 70%; margin: auto; line-height: 30px; color: #797979; } .poem-wrap p#poem { text-align: center; font-size: 25px; } .poem-wrap p#info { text-align: center; font-size: 15px; margin: 15px auto; } .poem-border { position: absolute; height: 2px; width: 27%; background-color: #797979; } .poem-right { right: 0; } .poem-left { left: 0; } @media (max-width: 685px) { .poem-border { width: 18%; } } @media (max-width: 500px) { .poem-wrap { margin-top: 60px; margin-bottom: 20px; border-top: 2px solid #797979; } .poem-wrap h1 { margin: 20px 6px; } .poem-border { display: none; } }"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"css/botui.min.css","permalink":"https://blog.hasaik.com/css/botui.min.css","excerpt":"","text":"/* * botui 0.3.9 * A JS library to build the UI for your bot * https://botui.org * * Copyright 2019, Moin Uddin * Released under the MIT license. */ a.botui-message-content-link:focus{outline:thin dotted}a.botui-message-content-link:focus:active,a.botui-message-content-link:focus:hover{outline:0}form.botui-actions-text{margin:0}button.botui-actions-buttons-button,input.botui-actions-text-input{margin:0;font-size:100%;line-height:normal;vertical-align:baseline}button.botui-actions-buttons-button::-moz-focus-inner,input.botui-actions-text-input::-moz-focus-inner{border:0;padding:0}button.botui-actions-buttons-button{cursor:pointer;-webkit-appearance:button} .botui-app-container{width:100%;height:100%;line-height:1}@media (min-width:400px){.botui-app-container{width:400px;height:500px;margin:0 auto}}.botui-container{width:100%;height:100%;overflow-y:auto;overflow-x:hidden}.botui-message{margin:10px 0;min-height:20px}.botui-message:after{display:block;content:\"\";clear:both}.botui-message-content{width:auto;max-width:75%;display:inline-block}.botui-message-content.human{float:right}.botui-message-content iframe{width:100%}.botui-message-content-image{margin:5px 0;display:block;max-width:200px;max-height:200px}.botui-message-content-link{text-decoration:underline}.profil{position:relative;border-radius:50%}.profil.human{float:right;margin-left:5px}.profil.agent{float:left;margin-right:5px}.profil>img{width:26px;height:26px;border:2px solid #e8e8e8}.profil>img.agent{content:url(http://decodemoji.com/img/logos/blue_moji_hat.svg);border-radius:50%}button.botui-actions-buttons-button{margin-top:10px;margin-bottom:10px}button.botui-actions-buttons-button:not(:last-child){margin-right:10px}@media (min-width:400px){.botui-actions-text-submit{display:none}}"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"css/message.css","permalink":"https://blog.hasaik.com/css/message.css","excerpt":"","text":"/* build time:Thu Mar 25 2021 13:28:38 GMT+0000 (Coordinated Universal Time)*/ .animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:none;animation-fill-mode:none}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut{-webkit-animation-duration:.75s;animation-duration:.75s}.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}100%{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}.c-message{top:16px;z-index:2000;width:300px;padding:14px 26px 14px 13px;box-sizing:border-box;position:fixed;right:16px;background-color:#fff;transition:opacity .3s,transform .3s,right .3s,top .4s;overflow:hidden;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:8px;border:1px solid #ebeef5}.c-message__title{font-weight:400;font-size:15px;color:#1f2d3d;margin:0}.c-message--icon{position:absolute;color:#fff;height:25px;width:25px;font-size:25px}.c-message a{border-bottom:1px solid rgba(131,145,165,.8);color:inherit;text-decoration:none;cursor:pointer!important;word-break:break-all}.c-message a:hover{border-bottom:1px solid #5c6775}.el-notification__content{font-size:13px;line-height:21px;margin:10px 0 0;color:#8391a5;text-align:justify}.el-notification__group{margin-left:35px}.c-message--success{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTE2NDE5MDE4MjQ0IiBjbGFzcz0iaWNvbiIgc3R5bGU9IiIgdmlld0JveD0iMCAwIDEwMjUgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3OTAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQuMDIzNDM3NSIgaGVpZ2h0PSIyNCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNODEwLjAxNTg1Nzc4IDMzMy40MzYwMTc3OEw0ODAuMzkyNTMzMzMgNzI0LjkwNjY2NjY3Yy0xNC42NjkzNjg4OSAxNi41MDQ2MDQ0NS0zOS42ODIyNzU1NSAxOC42MzU2NjIyMi01Ni45MzQ0IDQuODA3MTExMTFMMjI0LjE5OTExMTExIDU0MS4wNTU0MzExMWMtMi4xNTM4MTMzMy0yLjA1MzY4ODg5LTMuOTE5NjQ0NDUtNC4zMjY5Njg4OS01LjQ2NzAyMjIyLTYuNzA0OTI0NDQtNC4xMTUzNDIyMi01Ljc1NjAxNzc4LTYuNjExNjI2NjctMTIuNzU1NjI2NjctNi42MTE2MjY2Ny0yMC4zOTM1Mjg4OSAwLTE5LjM5MDAwODg5IDE1LjcxMDQzNTU1LTM1LjExNjM3MzMzIDM1LjEyMzItMzUuMTE2MzczMzMgNi4wOTczNTExMSAwIDExLjc1MjEwNjY3IDEuNjg3MzI0NDUgMTYuNzQ1ODEzMzMgNC40MTc5OTExIDAuNDgyNDE3NzggMC4yNzE5Mjg4OSAxLjAzNDI0IDAuNDc1NTkxMTEgMS40ODU5Mzc3OCAwLjc1NDM0NjY3bDAuNzIzNjI2NjcgMC40NTA1NmMwLjM1NzI2MjIyIDAuMjMzMjQ0NDUgMC43MzE1OTExMSAwLjQ3NTU5MTExIDEuMDk3OTU1NTUgMC43MjQ3NjQ0NWwxNzUuNjM3NjE3NzggMTE0LjMyNjE4NjY2YzIuMzE3NjUzMzMgMS41MjQ2MjIyMiA1LjM1ODkzMzMzIDEuMjEyODcxMTEgNy4zMjUwMTMzNC0wLjcxNTY2MjIybDMxMy41NzA0MTc3OC0zMDguMTQyMDhjMTIuMjQyNDg4ODktMTIuMDI0MDM1NTUgMzEuODU3Nzc3NzgtMTIuMDMyIDQ0LjExMjc4MjIyLTAuMDMwNzJDODE5LjczODE2ODg5IDMwMi4xNTI4MTc3OCA4MjAuNjQwNDI2NjcgMzIwLjgyMDMzNzc4IDgxMC4wMTU4NTc3OCAzMzMuNDM2MDE3NzhNNTE0LjIxMDcwMjIyLTIuMTI5OTJjLTI4MS41MDg5Nzc3OCAwLTUwOS43MjQ0NDQ0NSAyMjguMjE1NDY2NjctNTA5LjcyNDQ0NDQ0IDUwOS43MjQ0NDQ0NXMyMjguMjE1NDY2NjcgNTA5LjcyNDQ0NDQ1IDUwOS43MjQ0NDQ0NCA1MDkuNzI0NDQ0NDRjMjgxLjUyNDkwNjY3IDAgNTA5LjcyNDQ0NDQ1LTIyOC4yMTU0NjY2NyA1MDkuNzI0NDQ0NDUtNTA5LjcyNDQ0NDQ0Uzc5NS43MzU2MDg4OS0yLjEyOTkyIDUxNC4yMTA3MDIyMi0yLjEyOTkyeiIgZmlsbD0iIzY3YzIzYSIgcC1pZD0iMTc5MSI+PC9wYXRoPjwvc3ZnPg==) no-repeat 0 50%}.c-message--error{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTE2NDE3MjY0NzM4IiBjbGFzcz0iaWNvbiIgc3R5bGU9IiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0NzMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTk4My44IDMxMi43Qzk1OCAyNTEuNyA5MjEgMTk3IDg3NCAxNTBjLTQ3LTQ3LTEwMS43LTg0LTE2Mi43LTEwOS43QzY0OC4yIDEzLjUgNTgxLjEgMCA1MTIgMFMzNzUuOCAxMy41IDMxMi43IDQwLjNDMjUxLjcgNjYgMTk3IDEwMyAxNTAgMTUwYy00NyA0Ny04NCAxMDEuNy0xMDkuNyAxNjIuN0MxMy41IDM3NS44IDAgNDQyLjkgMCA1MTJzMTMuNSAxMzYuMiA0MC4zIDE5OS4zQzY2IDc3Mi4zIDEwMyA4MjcgMTUwIDg3NGM0NyA0NyAxMDEuOCA4My45IDE2Mi43IDEwOS43IDYzLjEgMjYuNyAxMzAuMiA0MC4zIDE5OS4zIDQwLjNzMTM2LjItMTMuNSAxOTkuMy00MC4zQzc3Mi4zIDk1OCA4MjcgOTIxIDg3NCA4NzRjNDctNDcgODMuOS0xMDEuOCAxMDkuNy0xNjIuNyAyNi43LTYzLjEgNDAuMy0xMzAuMiA0MC4zLTE5OS4zcy0xMy41LTEzNi4yLTQwLjItMTk5LjN6TTY2NC43IDYxMy44YzE0LjEgMTQuMSAxNC4xIDM2LjkgMCA1MC45LTcgNy0xNi4yIDEwLjUtMjUuNSAxMC41cy0xOC40LTMuNS0yNS41LTEwLjVMNTEyIDU2Mi45IDQxMC4yIDY2NC43Yy03IDctMTYuMiAxMC41LTI1LjUgMTAuNXMtMTguNC0zLjUtMjUuNS0xMC41Yy0xNC4xLTE0LjEtMTQuMS0zNi45IDAtNTAuOUw0NjEuMSA1MTIgMzU5LjMgNDEwLjJjLTE0LjEtMTQuMS0xNC4xLTM2LjkgMC01MC45IDE0LjEtMTQuMSAzNi45LTE0LjEgNTAuOSAwTDUxMiA0NjEuMWwxMDEuOC0xMDEuOGMxNC4xLTE0LjEgMzYuOS0xNC4xIDUwLjkgMCAxNC4xIDE0LjEgMTQuMSAzNi45IDAgNTAuOUw1NjIuOSA1MTJsMTAxLjggMTAxLjh6IiBwLWlkPSI0NDc0IiBmaWxsPSIjZjY2YzZiIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pMiIgY2xhc3M9IiI+PC9wYXRoPjwvc3ZnPg==) no-repeat 0 50%}.c-message--info{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTE2NDE5MTk2ODQ3IiBjbGFzcz0iaWNvbiIgc3R5bGU9IiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjExMzciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjUiIGhlaWdodD0iMjUiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTUxMiA3LjEzMDA3NDA4Yy0yNzguODQzNTQzNyAwLTUwNC44Njk5MjU5MiAyMjYuMDI2MzgyMjItNTA0Ljg2OTkyNTkyIDUwNC44Njk5MjU5MnMyMjYuMDI2MzgyMjIgNTA0Ljg2OTkyNTkyIDUwNC44Njk5MjU5MiA1MDQuODY5OTI1OTIgNTA0Ljg2OTkyNTkyLTIyNi4wMjYzODIyMiA1MDQuODY5OTI1OTItNTA0Ljg2OTkyNTkyLTIyNi4wMjYzODIyMi01MDQuODY5OTI1OTItNTA0Ljg2OTkyNTkyLTUwNC44Njk5MjU5MnpNNTkxLjE4NjkwNjA4IDY5Ny43NTMyOTY1OWMtMzguMDk4MjYxMzMgNTcuMjA1NjQ2MjItNzYuODU2NzM3MTggMTAxLjI4NDY3NDM3LTE0Mi4wNjI2Mjk5MyAxMDEuMjg0Njc0MzgtNDQuNTA2MjI1NzgtNy4yNjIzNTk3LTYyLjc5ODA1MTU1LTM5LjE0NjgzNzMzLTUzLjE2NjY4NjgyLTcxLjY1MjY5MzM0bDgzLjg4NjA4LTI3Ny44MzM4MDM4NWMyLjA1ODMxNTg1LTYuNzk2MzI1OTItMS4zNTkyNjUxOC0xNC4wNTg2ODU2My03LjU3MzA0ODg4LTE2LjI3MjM0NjA4LTYuMTc0OTQ3NTUtMi4xNzQ4MjQzLTE4LjI5MTgyNTc4IDUuODY0MjU4MzctMjguNzc3NTg1NzggMTcuMzU5NzU4MjJsLTUwLjcyMDAwOTQ5IDYxLjAxMTU4ODc1Yy0xLjM1OTI2NTE4LTEwLjI1Mjc0MzExLTAuMTU1MzQ0NTktMjcuMTg1MzAzNy0wLjE1NTM0NDU5LTM0LjAyMDQ2NTc4IDM4LjA5ODI2MTMzLTU3LjIwNTY0NjIyIDEwMC43MDIxMzIxNS0xMDIuMzMzMjUwMzcgMTQzLjE1MDA0MjA4LTEwMi4zMzMyNTAzNyA0MC4zNTA3NTc5MiA0LjExNjYzMTcgNTkuNDU4MTQyODIgMzYuMzg5NDcwODIgNTIuNDI4OCA3MS44NDY4NzQwN2wtODQuNDY4NjIyMjIgMjc5LjE5MzA2OTA0Yy0xLjEyNjI0ODMgNi4yOTE0NTYgMi4yMTM2NjA0NSAxMi42OTk0MjA0NSA3LjkyMjU3NDIyIDE0LjcxODkwMDE1IDYuMjEzNzgzNyAyLjE3NDgyNDMgMTkuMjYyNzI5NDgtNS44NjQyNTgzNyAyOS43ODczMjU2My0xNy4zNTk3NTgyM2w1MC42ODExNzMzMy02MC45NzI3NTI1OGMxLjM1OTI2NTE4IDEwLjI1Mjc0MzExLTAuOTMyMDY3NTUgMjguMTk1MDQzNTUtMC45MzIwNjc1NSAzNS4wMzAyMDU2MnpNNTc5Ljg4NTU4Njk3IDMzNC44NjgzMjgzYy0zMi4wNzg2NTgzNyAwLTU4LjA5ODg3NzYzLTIzLjM3OTM2MTE4LTU4LjA5ODg3NzY0LTU3Ljc4ODE4ODQ1czI2LjAyMDIxOTI1LTU3Ljc0OTM1MjMgNTguMDk4ODc3NjQtNTcuNzQ5MzUyMyA1OC4wOTg4Nzc2MyAyMy4zNzkzNjExOCA1OC4wOTg4Nzc2MiA1Ny43NDkzNTIzYzAgMzQuNDQ3NjYzNDEtMjYuMDIwMjE5MjUgNTcuNzg4MTg4NDUtNTguMDk4ODc3NjIgNTcuNzg4MTg4NDV6IiBmaWxsPSIjMjE5NmYzIiBwLWlkPSIxMTM4Ij48L3BhdGg+PC9zdmc+) no-repeat 0 50%}.c-message--warning{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTE2NDE4MzA1NDMxIiBjbGFzcz0iaWNvbiIgc3R5bGU9IiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjcxOTciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTUxMiAxMDI0QzIyOS4yMzIgMTAyNCAwIDc5NC43NjggMCA1MTIgMCAyMjkuMjMyIDIyOS4yMzIgMCA1MTIgMGMyODIuNzY4IDAgNTEyIDIyOS4yMzIgNTEyIDUxMkMxMDI0IDc5NC43NjggNzk0Ljc2OCAxMDI0IDUxMiAxMDI0ek01NjAgMjQwYzAtMjYuNTEyLTIxLjQ4OC00OC00OC00OHMtNDggMjEuNDg4LTQ4IDQ4bDAgMzUyYzAgMjYuNTEyIDIxLjQ4OCA0OCA0OCA0OHM0OC0yMS40ODggNDgtNDhMNTYwIDI0MHpNNTEyIDczNmMtMjYuNTEyIDAtNDggMjEuNDg4LTQ4IDQ4czIxLjQ4OCA0OCA0OCA0OCA0OC0yMS40ODggNDgtNDhTNTM4LjUxMiA3MzYgNTEyIDczNnoiIHAtaWQ9IjcxOTgiIGZpbGw9IiNlNmEyM2MiPjwvcGF0aD48L3N2Zz4=) no-repeat 0 50%}.c-message--close{position:absolute;right:10px;color:#999;text-decoration:none;cursor:pointer;font-size:25px;top:0;line-height:34px;display:block;height:20px;font-weight:100}.c-message--close:hover{color:#666}@keyframes messageFadeInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}100%{-webkit-transform:none;transform:none}}.c-message.messageFadeInDown{-webkit-animation-duration:.6s;animation-duration:.6s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:messageFadeInDown;animation-name:messageFadeInDown}@keyframes messageFadeOutUp{0%{opacity:1}100%{opacity:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}}.c-message.messageFadeOutUp{-webkit-animation-duration:.6s;animation-duration:.6s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:messageFadeOutUp;animation-name:messageFadeOutUp} /* rebuild by neat */"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"friends/index.html","permalink":"https://blog.hasaik.com/friends/index.html","excerpt":"","text":"🎉小伙伴 友链更新通知 由于近期对友链系统进行了重做,原链接失效的小伙伴请按照下方交换友链的步骤进行填写。在新的友链系统中,您随时可以对自己的信息进行修改而无需等待博主更新。 添加友链流程 友链申明 更新自己的博客链接 其他方式添加友链 第一步:新建 Gitee Issue 按照格式填写并提交 12345678{ "title": "", "avatar": "", "screenshot": "", "url": "", "description": "", "group": ""}为了提高图片加载速度,建议优化头像和截图:1.打开 压缩图 上传自己的头像,将图片尺寸调整到 96px 后下载。2.将压缩后的图片上传到 去不图床 或者 sm.ms 图床 并使用此图片链接作为头像。3.重复上述步骤,把压缩网站截图并把尺寸调整到 540x360 以下。 第二步:添加友链并等待管理员审核 请添加本站到您的友链中,如果您也使用 issue 作为友链源,只需要告知您的友链源仓库即可。123456title: Xu’s Blog # 博客名avatar: https://hasaik.com/images/avatar.jpg # 头像链接url: https://hasaik.com # 博客链接screenshot: https://7.dusays.com/2020/10/23/d6d2f3589e979.png # 网站截图description: 简单不先于复杂,而是在复杂之后。 # 网站描述tags: [Java, 前端] # 标签待管理员审核通过,回来刷新即可生效。 🔔 站点失效、停止维护、内容不当都可能被取消友链 🔔 禁链不尊重他人劳动成果,转载、引用不加出处,恶意行为的站点 🔔 本站会定期检查并清理无效的、单方面的友链,如更换信息请留言,谢谢合作 🔔 加入友链后会在本站任意留言区获得小伙伴徽章(以邮箱判定)一枚哦,如果没有请联系我如果是自己创建的 issue ,可以自己修改。 如果是管理员创建的,请自己重新创建一份,然后让管理员删掉旧的。如果你不会使用 Gitee Issue 提交友链,那么请按照以下步骤申请友链。 第一步:按照下面格式留言 12345678{ "title": "", # 站点名字 "avatar": "", # 头像 "screenshot": "", # 站点截图 "url": "", # 站点地址 "description": "", # 描述 "group": "" # 分组,可选值有[技术大佬、朋友们、虐狗博主]}为了提高图片加载速度,建议优化头像和截图:1.打开 压缩图 上传自己的头像,将图片尺寸调整到 96px 后下载。2.将压缩后的图片上传到 去不图床 或者 sm.ms 图床 并使用此图片链接作为头像。3.重复上述步骤,把压缩网站截图并把尺寸调整到 540x360 以下。 第二步:添加本站友链并等待管理员审核 请添加本站到您的友链中(信息按需取)123456title: Xu’s Blog # 博客名avatar: https://hasaik.com/images/avatar.jpg # 头像链接url: https://hasaik.com # 博客链接screenshot: https://7.dusays.com/2020/10/23/d6d2f3589e979.png # 网站截图description: 简单不先于复杂,而是在复杂之后。 # 网站描述tags: [Java, 前端] # 标签待管理员核实以后,会帮您到 Gitee Issue 添加友链。 .issues-api h2:first-child{ margin-top: -50px; }"},{"title":"小伙伴们","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"friends/index_backup.html","permalink":"https://blog.hasaik.com/friends/index_backup.html","excerpt":"","text":"申请友链: 添加流程 清理记录 请先添加本站链接 名称:Xu’s Blog链接:https://blog.hasaik.com头像:https://blog.hasaik.com/images/avatar.jpg描述:简单不先于复杂,而是在复杂之后!标签:Java,前端背景颜色(若有):#967ADC文字颜色(若有):#fff 下方评论区按此格式申请友链 1234- name: Xu’s Blog # 博客名 avatar: https://blog.hasaik.com/images/avatar.jpg # 头像链接 url: https://blog.hasaik.com # 博客链接 tags: [Java, 前端] # 标签 等待本站添加贵站 友链申明 🔔 站点失效、停止维护、内容不当都可能被取消友链🔔 禁链不尊重他人劳动成果,转载、引用不加出处,恶意行为的站点🔔 本站会定期检查并清理无效的、单方面的友链,如更换信息请留言,谢谢合作🔔 加入友链后会在本站任意留言区获得小伙伴徽章(以邮箱判定)一枚哦,如果没有请联系我 互链成功 已经添加的友链不会轻易删除。如果您已经移除本站,本站也将移除友链 如果出现误清理请重新申请即可! 2020-08-20 朱纯树博客 - https://si***og.cn站点无法访问Emil’s blog - https://blog.hv***g.com/站点未开启Https details summary:hover:after{ right: 4px; } .links-tips-friends { font-size: 12px; padding: 4px 4px; background: #6cf; color: #fff; border-radius: 2px; margin: 0 3px; vertical-align: 1px; }"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/DigitalRain.js","permalink":"https://blog.hasaik.com/js/DigitalRain.js","excerpt":"","text":"window.onload = function(){ //获取画布对象 var canvas = document.getElementById(\"codeRainCanvas\"); //获取画布的上下文 var context =canvas.getContext(\"2d\"); var s = window.screen; var W = canvas.width = s.width; var H = canvas.height; //获取浏览器屏幕的宽度和高度 //var W = window.innerWidth; //var H = window.innerHeight; //设置canvas的宽度和高度 canvas.width = W; canvas.height = H; //每个文字的字体大小 var fontSize = 12; //计算列 var colunms = Math.floor(W /fontSize); //记录每列文字的y轴坐标 var drops = []; //给每一个文字初始化一个起始点的位置 for(var i=0;i 0.99){ drops[i] = 0; } drops[i]++; } }; function randColor(){//随机颜色 var r = Math.floor(Math.random() * 256); var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); return \"rgb(\"+r+\",\"+g+\",\"+b+\")\"; } draw(); setInterval(draw,35); };"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/botui-message.js","permalink":"https://blog.hasaik.com/js/botui-message.js","excerpt":"","text":"$(function () { var botui = new BotUI(\"hello-xuxuy\"); botui.message.add({delay: 800, content: \"Hi, there👋\"}).then(function () { botui.message.add({delay: 1100, content: \"这里是博主小屋✨ \"}).then(function () { botui.message.add({delay: 1100, content: \"一个积极向上的Boy~~\"}).then(function () { botui.action.button({ delay: 1600, action: [{text: \"然后呢? 😃\", value: \"sure\"}, {text: \"少废话! 🙄\", value: \"skip\"}] }).then(function (a) { \"sure\" == a.value && sure(); \"skip\" == a.value && end() }) }) }) }); var sure = function () { botui.message.add({delay: 600, content: \"😘\"}).then(function () { secondPart() }) }, end = function () { botui.message.add({ delay: 600, content: \"告辞了您嘞!\" }) }, secondPart = function () { botui.message.add({delay: 1500, content: \"现就职于百度\"}).then(function () { botui.message.add({delay: 1500, content: \"一枚标准90后程序猿\"}).then(function () { botui.message.add({delay: 1200, content: \"将敲代码看成一种快乐\"}).then(function () { botui.message.add({ delay: 1500, content: \"拥有两年 Java 开发经验,熟练使用 Spring Boot 框架,了解 Redis 等缓存组件。对前后端分离模式,可视化开发深入理解,对 Vue 有丰富的开发经验,具备一定的框架设计能力。\" }).then(function () { botui.message.add({delay: 1800, content: \"喜欢健身、接触新事物、打游戏\"}).then(function () { botui.action.button({ delay: 1100, action: [{text: \"个人简介是什么呢?🤔\", value: \"what-info\"}] }).then(function () { thirdPart() }) }) }) }) }) }) }, thirdPart = function () { botui.message.add({delay: 1e3, content: \"脚下的路如果不是你自己的选择,那旅程的终点在哪,也没人知道 ...\"}).then(function () { botui.action.button({delay: 1500, action: [{text: \"域名有什么含义吗?\", value: \"why-domain\"}]}).then(function (a) { fourthPart() }) }) }, fourthPart = function () { botui.message.add({delay: 1e3, content: \"emmmmm,当时喜欢玩联盟压缩(快乐风男),所以就以他的台词作为域名了,哈塞K\"}).then(function () { botui.message.add({delay: 1600, content: \"那么,相遇就是缘分,赏个赞吧 ^_^\"}) }) } })"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/canvas-clock.js","permalink":"https://blog.hasaik.com/js/canvas-clock.js","excerpt":"","text":"(function () { var WINDOW_WIDTH = 820; var WINDOW_HEIGHT = 250; var RADIUS = 7; //球半径 var NUMBER_GAP = 10; //数字之间的间隙 var u = 0.65; //碰撞能量损耗系数 var context; //Canvas绘制上下文 var balls = []; //存储彩色的小球 const colors = [\"#33B5E5\", \"#0099CC\", \"#AA66CC\", \"#9933CC\", \"#99CC00\", \"#669900\", \"#FFBB33\", \"#FF8800\", \"#FF4444\", \"#CC0000\"]; //彩色小球的颜色 var currentNums = []; //屏幕显示的8个字符 var digit = [ [ [0, 0, 1, 1, 1, 0, 0], [0, 1, 1, 0, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 0, 1, 1, 0], [0, 0, 1, 1, 1, 0, 0] ],//0 [ [0, 0, 0, 1, 1, 0, 0], [0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [1, 1, 1, 1, 1, 1, 1] ],//1 [ [0, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 1, 1] ],//2 [ [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0] ],//3 [ [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0], [0, 1, 1, 0, 1, 1, 0], [1, 1, 0, 0, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1] ],//4 [ [1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0] ],//5 [ [0, 0, 0, 0, 1, 1, 0], [0, 0, 1, 1, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0], [1, 1, 0, 1, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0] ],//6 [ [1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0] ],//7 [ [0, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0] ],//8 [ [0, 1, 1, 1, 1, 1, 0], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1, 0], [0, 0, 0, 1, 1, 0, 0], [0, 1, 1, 0, 0, 0, 0] ],//9 [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]//: ]; function drawDatetime(cxt) { var nums = []; // 时间字体颜色 context.fillStyle = \"#005eac\" var date = new Date(); var offsetX = 70, offsetY = 30; var hours = date.getHours(); var num1 = Math.floor(hours / 10); var num2 = hours % 10; nums.push({num: num1}); nums.push({num: num2}); nums.push({num: 10}); //冒号 var minutes = date.getMinutes(); var num1 = Math.floor(minutes / 10); var num2 = minutes % 10; nums.push({num: num1}); nums.push({num: num2}); nums.push({num: 10}); //冒号 var seconds = date.getSeconds(); var num1 = Math.floor(seconds / 10); var num2 = seconds % 10; nums.push({num: num1}); nums.push({num: num2}); for (var x = 0; x < nums.length; x++) { nums[x].offsetX = offsetX; offsetX = drawSingleNumber(offsetX, offsetY, nums[x].num, cxt); //两个数字连一块,应该间隔一些距离 if (x < nums.length - 1) { if ((nums[x].num != 10) && (nums[x + 1].num != 10)) { offsetX += NUMBER_GAP; } } } //说明这是初始化 if (currentNums.length == 0) { currentNums = nums; } else { //进行比较 for (var index = 0; index < currentNums.length; index++) { if (currentNums[index].num != nums[index].num) { //不一样时,添加彩色小球 addBalls(nums[index]); currentNums[index].num = nums[index].num; } } } renderBalls(cxt); updateBalls(); return date; } function addBalls(item) { var num = item.num; var numMatrix = digit[num]; for (var y = 0; y < numMatrix.length; y++) { for (var x = 0; x < numMatrix[y].length; x++) { if (numMatrix[y][x] == 1) { var ball = { offsetX: item.offsetX + RADIUS + RADIUS * 2 * x, offsetY: 30 + RADIUS + RADIUS * 2 * y, color: colors[Math.floor(Math.random() * colors.length)], g: 1.5 + Math.random(), vx: Math.pow(-1, Math.ceil(Math.random() * 10)) * 4 + Math.random(), vy: -5 } balls.push(ball); } } } } function renderBalls(cxt) { for (var index = 0; index < balls.length; index++) { cxt.beginPath(); cxt.fillStyle = balls[index].color; cxt.arc(balls[index].offsetX, balls[index].offsetY, RADIUS, 0, 2 * Math.PI); cxt.fill(); } } function updateBalls() { var i = 0; for (var index = 0; index < balls.length; index++) { var ball = balls[index]; ball.offsetX += ball.vx; ball.offsetY += ball.vy; ball.vy += ball.g; if (ball.offsetY > (WINDOW_HEIGHT - RADIUS)) { ball.offsetY = WINDOW_HEIGHT - RADIUS; ball.vy = -ball.vy * u; } if (ball.offsetX > RADIUS && ball.offsetX < (WINDOW_WIDTH - RADIUS)) { balls[i] = balls[index]; i++; } } //去除出边界的球 for (; i < balls.length; i++) { balls.pop(); } } function drawSingleNumber(offsetX, offsetY, num, cxt) { var numMatrix = digit[num]; for (var y = 0; y < numMatrix.length; y++) { for (var x = 0; x < numMatrix[y].length; x++) { if (numMatrix[y][x] == 1) { cxt.beginPath(); cxt.arc(offsetX + RADIUS + RADIUS * 2 * x, offsetY + RADIUS + RADIUS * 2 * y, RADIUS, 0, 2 * Math.PI); cxt.fill(); } } } cxt.beginPath(); offsetX += numMatrix[0].length * RADIUS * 2; return offsetX; } var canvas = document.getElementById(\"particleClockCanvas\"); canvas.width = WINDOW_WIDTH; canvas.height = WINDOW_HEIGHT; context = canvas.getContext(\"2d\"); //记录当前绘制的时刻 var currentDate = new Date(); var timer = null; // 设置定时器之前先清除 if (timer !== null) { debugger clearInterval(timer) } timer = setInterval(function () { //清空整个Canvas,重新绘制内容 context.clearRect(0, 0, context.canvas.width, context.canvas.height); drawDatetime(context); }, 50) })();"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/crash_cheat.js","permalink":"https://blog.hasaik.com/js/crash_cheat.js","excerpt":"","text":"$(function () { var OriginTitle = document.title; var titleTime; document.addEventListener('visibilitychange', function () { // 解决pjax冲突bug if (document.title !== '(つェ⊂) 我藏好了哦~') { OriginTitle = document.title } if (document.hidden) { $('[rel=\"icon\"]').attr('href', \"https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/funny.png\"); $('[rel=\"shortcut icon\"]').attr('href', \"https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/funny.png\"); document.title = '(つェ⊂) 我藏好了哦~ '; clearTimeout(titleTime); } else { $('[rel=\"icon\"]').attr('href', \"https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/favicon.png\"); $('[rel=\"shortcut icon\"]').attr('href', \"https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/favicon.png\"); document.title = 'o(^▽^)o 被你发现啦~ '; titleTime = setTimeout(function () { document.title = OriginTitle; }, 2000); } }); })"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/fairyDustCursor.js","permalink":"https://blog.hasaik.com/js/fairyDustCursor.js","excerpt":"","text":"(function fairyDustCursor() { var possibleColors = [\"#D61C59\", \"#E7D84B\", \"#1B8798\"] var width = window.innerWidth; var height = window.innerHeight; var cursor = {x: width/2, y: width/2}; var particles = []; function init() { bindEvents(); loop(); } // Bind events that are needed function bindEvents() { document.addEventListener('mousemove', onMouseMove); document.addEventListener('touchmove', onTouchMove); document.addEventListener('touchstart', onTouchMove); window.addEventListener('resize', onWindowResize); } function onWindowResize(e) { width = window.innerWidth; height = window.innerHeight; } function onTouchMove(e) { if( e.touches.length > 0 ) { for( var i = 0; i < e.touches.length; i++ ) { addParticle( e.touches[i].clientX, e.touches[i].clientY, possibleColors[Math.floor(Math.random()*possibleColors.length)]); } } } function onMouseMove(e) { cursor.x = e.clientX; cursor.y = e.clientY; addParticle( cursor.x, cursor.y, possibleColors[Math.floor(Math.random()*possibleColors.length)]); } function addParticle(x, y, color) { var particle = new Particle(); particle.init(x, y, color); particles.push(particle); } function updateParticles() { for( var i = 0; i < particles.length; i++ ) { particles[i].update(); } for( var i = particles.length -1; i >= 0; i-- ) { if( particles[i].lifeSpan < 0 ) { particles[i].die(); particles.splice(i, 1); } } } function loop() { requestAnimationFrame(loop); updateParticles(); } function Particle() { this.character = \"*\"; this.lifeSpan = 120; //ms this.initialStyles ={ \"position\": \"fixed\", \"top\": \"0\", //必须加 \"display\": \"block\", \"pointerEvents\": \"none\", \"z-index\": \"10000000\", \"fontSize\": \"20px\", \"will-change\": \"transform\" }; this.init = function(x, y, color) { this.velocity = { x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2), y: 1 }; this.position = {x: x - 10, y: y - 20}; this.initialStyles.color = color; console.log(color); this.element = document.createElement('span'); this.element.innerHTML = this.character; applyProperties(this.element, this.initialStyles); this.update(); document.body.appendChild(this.element); }; this.update = function() { this.position.x += this.velocity.x; this.position.y += this.velocity.y; this.lifeSpan--; this.element.style.transform = \"translate3d(\" + this.position.x + \"px,\" + this.position.y + \"px,0) scale(\" + (this.lifeSpan / 120) + \")\"; } this.die = function() { this.element.parentNode.removeChild(this.element); } } function applyProperties( target, properties ) { for( var key in properties ) { target.style[ key ] = properties[ key ]; } } init(); })();"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/message.js","permalink":"https://blog.hasaik.com/js/message.js","excerpt":"","text":"$.extend({ message: function (a) { var b = { title: \"\", message: \" 操作成功\", time: \"3000\", type: \"success\", showClose: !0, autoClose: !0, onClose: function () {} }; \"string\" == typeof a && (b.message = a), \"object\" == typeof a && (b = $.extend({}, b, a)); var c, d, e, f = b.showClose ? '×' : \"\", g = \"\" !== b.title ? '' + b.title + \"\" : \"\", h = '' + g + '' + b.message + \"\" + f + \"\", i = $(\"body\"), j = $(h); d = function () { j.addClass(\"slideOutRight\"), j.one(\"webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend\", function () { e() }) }, e = function () { j.remove(), b.onClose(b), clearTimeout(c) }, $(\".c-message\").remove(), i.append(j), j.one(\"webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend\", function () { j.removeClass(\"messageFadeInDown\") }), i.on(\"click\", \".c-message--close\", function (a) { d() }), b.autoClose && (c = setTimeout(function () { d() }, b.time)) } })"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/tag-color.js","permalink":"https://blog.hasaik.com/js/tag-color.js","excerpt":"","text":"// 彩色标签云 var allTags = document.getElementsByClassName('tag-list'); if (allTags.length > 0) { var colors = ['#a1c4fd, #c2e9fb', '#fdcbf1, #fdcbf1, #e6dee9', '#e0c3fc, #8ec5fc', '#cd9cf2, #f6f3ff'] var tags = allTags[0].getElementsByTagName('a'); for (var i = tags.length - 1; i >= 0; i--) { var colorGradient = colors[Math.floor(Math.random() * colors.length)]; tags[i].style.background = \"linear-gradient(to right, \" + colorGradient + \")\"; tags[i].style.color = \"#fff\"; } }"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/typed.js","permalink":"https://blog.hasaik.com/js/typed.js","excerpt":"","text":"/*! * * typed.js - A JavaScript Typing Animation Library * Author: Matt Boldt * Version: v2.0.11 * Url: https://github.com/mattboldt/typed.js * License(s): MIT * */ (function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.Typed=e():t.Typed=e()})(this,function(){return function(t){function e(n){if(s[n])return s[n].exports;var i=s[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var s={};return e.m=t,e.c=s,e.p=\"\",e(0)}([function(t,e,s){\"use strict\";function n(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}Object.defineProperty(e,\"__esModule\",{value:!0});var i=function(){function t(t,e){for(var s=0;s=t.length?s.doneTyping(t,e):s.keepTyping(t,e,i),s.temporaryPause&&(s.temporaryPause=!1,s.options.onTypingResumed(s.arrayPos,s))},n)},n))}},{key:\"keepTyping\",value:function(t,e,s){0===e&&(this.toggleBlinking(!1),this.options.preStringTyped(this.arrayPos,this)),e+=s;var n=t.substr(0,e);this.replaceText(n),this.typewrite(t,e)}},{key:\"doneTyping\",value:function(t,e){var s=this;this.options.onStringTyped(this.arrayPos,this),this.toggleBlinking(!0),this.arrayPos===this.strings.length-1&&(this.complete(),this.loop===!1||this.curLoop===this.loopCount)||(this.timeout=setTimeout(function(){s.backspace(t,e)},this.backDelay))}},{key:\"backspace\",value:function(t,e){var s=this;if(this.pause.status===!0)return void this.setPauseStatus(t,e,!0);if(this.fadeOut)return this.initFadeOut();this.toggleBlinking(!1);var n=this.humanizer(this.backSpeed);this.timeout=setTimeout(function(){e=o.htmlParser.backSpaceHtmlChars(t,e,s);var n=t.substr(0,e);if(s.replaceText(n),s.smartBackspace){var i=s.strings[s.arrayPos+1];i&&n===i.substr(0,e)?s.stopNum=e:s.stopNum=0}e>s.stopNum?(e--,s.backspace(t,e)):et.arrayPos?t.typewrite(t.strings[t.sequence[t.arrayPos]],0):(t.typewrite(t.strings[0],0),t.arrayPos=0)},this.fadeOutDelay)}},{key:\"replaceText\",value:function(t){this.attr?this.el.setAttribute(this.attr,t):this.isInput?this.el.value=t:\"html\"===this.contentType?this.el.innerHTML=t:this.el.textContent=t}},{key:\"bindFocusEvents\",value:function(){var t=this;this.isInput&&(this.el.addEventListener(\"focus\",function(e){t.stop()}),this.el.addEventListener(\"blur\",function(e){t.el.value&&0!==t.el.value.length||t.start()}))}},{key:\"insertCursor\",value:function(){this.showCursor&&(this.cursor||(this.cursor=document.createElement(\"span\"),this.cursor.className=\"typed-cursor\",this.cursor.innerHTML=this.cursorChar,this.el.parentNode&&this.el.parentNode.insertBefore(this.cursor,this.el.nextSibling)))}}]),t}();e[\"default\"]=a,t.exports=e[\"default\"]},function(t,e,s){\"use strict\";function n(t){return t&&t.__esModule?t:{\"default\":t}}function i(t,e){if(!(t instanceof e))throw new TypeError(\"Cannot call a class as a function\")}Object.defineProperty(e,\"__esModule\",{value:!0});var r=Object.assign||function(t){for(var e=1;e"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/typewriter-color.js","permalink":"https://blog.hasaik.com/js/typewriter-color.js","excerpt":"","text":"var typewriterColor = function (r) { function t() { return b[Math.floor(Math.random() * b.length)] } function e() { return String.fromCharCode(94 * Math.random() + 33) } function n(r) { for (var n = document.createDocumentFragment(), i = 0; r > i; i++) { var l = document.createElement(\"span\"); l.textContent = e(), l.style.color = t(), n.appendChild(l) } return n } function i() { var t = o[c.skillI]; c.step ? c.step-- : (c.step = g, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : \"forward\" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = \"backward\", c.delay = a) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % o.length, c.direction = \"forward\")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(s, s + c.prefixP) : Math.min(s, t.length - c.skillP))), setTimeout(i, d) } /*以下内容自定义修改*/ var l = \"\", o = [\"终有溺水替沧海,再无相思寄巫山~\", ].map(function (r) { return r + \"\" }), a = 2, g = 1, s = 5, d = 75, b = [\"rgb(110,64,170)\", \"rgb(150,61,179)\", \"rgb(191,60,175)\", \"rgb(228,65,157)\", \"rgb(254,75,131)\", \"rgb(255,94,99)\", \"rgb(255,120,71)\", \"rgb(251,150,51)\", \"rgb(226,183,47)\", \"rgb(198,214,60)\", \"rgb(175,240,91)\", \"rgb(127,246,88)\", \"rgb(82,246,103)\", \"rgb(48,239,130)\", \"rgb(29,223,163)\", \"rgb(26,199,194)\", \"rgb(35,171,216)\", \"rgb(54,140,225)\", \"rgb(76,110,219)\", \"rgb(96,84,200)\"], c = {text: \"\", prefixP: -s, skillI: 0, skillP: 0, direction: \"forward\", delay: a, step: g}; i() }; typewriterColor(document.getElementById('binft'));"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"photos/index.html","permalink":"https://blog.hasaik.com/photos/index.html","excerpt":"","text":"🎃我的相册 美食 生活 旅行 无名 宇宙 关于光影回味无穷,唇齿留香 美食,顾名思义就是美味的食物,各个地区也有自己喜爱的口味,比如上海等地喜酸甜,四川重庆喜麻辣,东北地区则口味和盐会重一点。随着生活水平的提高,食物也不在只是充饥了 。美食不仅仅是简单的味觉感受,更是一种精神享受。所以,在做美食这一块也是很重要的。 彼方为谁,无我有问 有人说生活就是生下来,活下去;生容易,活却难;七情六欲为人之本性,如果某一天你忽然对这个世界没了欲望,你也就不会留恋人的生活了,这看上去是个悖论,可事实就是如此,我们为了欲望痛苦,又因为欲望而生存! 九月露湿,待君之前 有人说要么读书,要么旅行,身体和灵魂,总有一个要在路上;曾梦想仗剑走天涯,看一看世间的繁华。这是一个出走的过程,在短暂的时间里与自己对话,在时光的缝隙中遇见真正的自己,变得淡然与豁达,至于风景这只是附赠品。骚年,愿你走出半生,归来仍是少年。一辈子是场修行。短的是旅途,长的是人生。 Interesting 收集一些有意思的图片 天地玄黄,宇宙洪荒 谈起宇宙,犹记得我很小的时候就喜欢抬头看着天空,白天看着云,晚上看着星星,也让老妈买了很多天文学的书籍,对于宇宙的好奇我从未停止过,古往今来,人类对于宇宙的探索也从未停止,然而我们对于宇宙或者说外太空的了解却是知之甚少,璀璨星空中的奥妙让人类为之着迷,但也同样值得我们敬畏。 (事实情况是,我们目前只知道银河系是一个螺旋星系,它究竟是怎样的轮廓模样,其实我们完全不知道。现在的我们还不能发射足够远的探测器,去观测我们所在的家乡——银河系,究竟长着什么样子。而且,一张宇宙的照片可能就有N亿像素,随随便便就占了N个G,之前轰动一时的黑洞照片,光"洗照片"就洗了两年,内存有7K+ TB大小,此处展示的主要是对宇宙的猜想和艺术照…) 摄影是什么?摄影是光与影的结合,它将光线永远停留在了物体上,它将瞬间变为永恒。我不是一个专业的摄影者,纯粹只是个兴趣爱好,甚至谈不上摄影称之为拍照更合适,因为大多数时候我都是用手机拍的照片,弄这个相册于此处主要展示一些我自己拍的还有看到的有意思的一些图片。"},{"title":"","date":"2021-03-25T13:28:10.077Z","updated":"2021-03-25T13:28:10.077Z","comments":true,"path":"shuoshuo/index.html","permalink":"https://blog.hasaik.com/shuoshuo/index.html","excerpt":"","text":".article .article-entry #shuoshuo_content ul li:before{ content: none; } .shuoshuoimg:hover{ transform: none; } .cbp_tmtimeline>li .cbp_tmlabel,.cbp_tmtimeline>li .cbp_tmlabel p{ cursor: unset; } #shuoshuo_content button { cursor: url(https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/ayuda.cur), auto; } .power a{ cursor: url(https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/site-img/ayuda.cur), auto; }"},{"title":"所有标签","date":"2021-03-25T13:28:10.077Z","updated":"2021-03-25T13:28:10.077Z","comments":true,"path":"tags/index.html","permalink":"https://blog.hasaik.com/tags/index.html","excerpt":"","text":""},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/cursor/explosion.js","permalink":"https://blog.hasaik.com/js/cursor/explosion.js","excerpt":"","text":"/* Copyright (C) 2013 Justin Windle sketch.min.js, http://soulwire.co.uk */ var Sketch=function(){\"use strict\";function e(e){return\"[object Array]\"==Object.prototype.toString.call(e)}function t(e){return\"function\"==typeof e}function n(e){return\"number\"==typeof e}function o(e){return\"string\"==typeof e}function r(e){return E[e]||String.fromCharCode(e)}function i(e,t,n){for(var o in t)(n||!e.hasOwnProperty(o))&&(e[o]=t[o]);return e}function u(e,t){return function(){e.apply(t,arguments)}}function a(e){var n={};for(var o in e)n[o]=t(e[o])?u(e[o],e):e[o];return n}function c(e){function n(n){t(n)&&n.apply(e,[].splice.call(arguments,1))}function u(e){for(_=0;_= 0; i--) { particles[i].draw(clickparticle); } }; //按下时显示效果,mousedown 或者 click document.addEventListener(\"click\", function(e) { var max, j; //排除一些元素 \"TEXTAREA\" !== e.target.nodeName && \"INPUT\" !== e.target.nodeName && \"A\" !== e.target.nodeName && \"I\" !== e.target.nodeName && \"IMG\" !== e.target.nodeName && function() { for (max = random(15, 20), j = 0; j < max; j++) clickparticle.spawn(e.clientX, e.clientY); }(); }); }"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/cursor/fireworks.js","permalink":"https://blog.hasaik.com/js/cursor/fireworks.js","excerpt":"","text":"class Circle { constructor({ origin, speed, color, angle, context }) { this.origin = origin this.position = { ...this.origin } this.color = color this.speed = speed this.angle = angle this.context = context this.renderCount = 0 } draw() { this.context.fillStyle = this.color this.context.beginPath() this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2) this.context.fill() } move() { this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3) this.renderCount++ } } class Boom { constructor ({ origin, context, circleCount = 16, area }) { this.origin = origin this.context = context this.circleCount = circleCount this.area = area this.stop = false this.circles = [] } randomArray(range) { const length = range.length const randomIndex = Math.floor(length * Math.random()) return range[randomIndex] } randomColor() { const range = ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] return '#' + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) } randomRange(start, end) { return (end - start) * Math.random() + start } init() { for(let i = 0; i < this.circleCount; i++) { const circle = new Circle({ context: this.context, origin: this.origin, color: this.randomColor(), angle: this.randomRange(Math.PI - 1, Math.PI + 1), speed: this.randomRange(1, 6) }) this.circles.push(circle) } } move() { this.circles.forEach((circle, index) => { if (circle.position.x > this.area.width || circle.position.y > this.area.height) { return this.circles.splice(index, 1) } circle.move() }) if (this.circles.length == 0) { this.stop = true } } draw() { this.circles.forEach(circle => circle.draw()) } } class CursorSpecialEffects { constructor() { this.computerCanvas = document.createElement('canvas') this.renderCanvas = document.createElement('canvas') this.computerContext = this.computerCanvas.getContext('2d') this.renderContext = this.renderCanvas.getContext('2d') this.globalWidth = window.innerWidth this.globalHeight = window.innerHeight this.booms = [] this.running = false } handleMouseDown(e) { const boom = new Boom({ origin: { x: e.clientX, y: e.clientY }, context: this.computerContext, area: { width: this.globalWidth, height: this.globalHeight } }) boom.init() this.booms.push(boom) this.running || this.run() } handlePageHide() { this.booms = [] this.running = false } init() { const style = this.renderCanvas.style style.position = 'fixed' style.top = style.left = 0 style.zIndex = '999999999999999999999999999999999999999999' style.pointerEvents = 'none' style.width = this.renderCanvas.width = this.computerCanvas.width = this.globalWidth style.height = this.renderCanvas.height = this.computerCanvas.height = this.globalHeight document.body.append(this.renderCanvas) window.addEventListener('mousedown', this.handleMouseDown.bind(this)) window.addEventListener('pagehide', this.handlePageHide.bind(this)) } run() { this.running = true if (this.booms.length == 0) { return this.running = false } requestAnimationFrame(this.run.bind(this)) this.computerContext.clearRect(0, 0, this.globalWidth, this.globalHeight) this.renderContext.clearRect(0, 0, this.globalWidth, this.globalHeight) this.booms.forEach((boom, index) => { if (boom.stop) { return this.booms.splice(index, 1) } boom.move() boom.draw() }) this.renderContext.drawImage(this.computerCanvas, 0, 0, this.globalWidth, this.globalHeight) } } const cursorSpecialEffects = new CursorSpecialEffects() cursorSpecialEffects.init()"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/cursor/love.min.js","permalink":"https://blog.hasaik.com/js/cursor/love.min.js","excerpt":"","text":"!function(e,t,a){function n(){c(\".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}\"),o(),r()}function r(){for(var e=0;e"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/cursor/text.js","permalink":"https://blog.hasaik.com/js/cursor/text.js","excerpt":"","text":"var a_idx = 0; jQuery(document).ready(function($) { $(\"body\").click(function(e) { var a = new Array(\"富强\", \"民主\", \"文明\", \"和谐\", \"自由\", \"平等\", \"公正\" ,\"法治\", \"爱国\", \"敬业\", \"诚信\", \"友善\"); var $i = $(\"\").text(a[a_idx]); var x = e.pageX, y = e.pageY; $i.css({ \"z-index\": 99999, \"top\": y - 28, \"left\": x - a[a_idx].length * 8, \"position\": \"absolute\", \"color\": \"#ff7a45\" }); $(\"body\").append($i); $i.animate({ \"top\": y - 180, \"opacity\": 0 }, 1500, function() { $i.remove(); }); a_idx = (a_idx + 1) % a.length; }); });"},{"title":"","date":"2021-03-25T13:28:10.069Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"js/sakura.js","permalink":"https://blog.hasaik.com/js/sakura.js","excerpt":"","text":"var stop, staticx; var img = new Image(); img.src = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUgAAAEwCAYAAADVZeifAAAACXBIWXMAAACYAAAAmAGiyIKYAAAHG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBSaWdodHM9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9yaWdodHMvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NDFDMjQxQjYyNjIwNjgxMTgwODNEMjE2MDAzOTU1NDQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDozNDVjOWViOC04NDc4LTFkNDctOGRjMi0yZDkyOGNhYTYxZWQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6YjAzN2ZiMGItNTU5Mi0xYjRkLWJjZGQtOWU4NGExMDJiMGM2IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE4LTA1LTA5VDE0OjQ5OjM3KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOC0wNS0wOVQxNDo1MToyNSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOC0wNS0wOVQxNDo1MToyNSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjEyMjVlZWE3LTEyY2QtMTY0NC04ZDAzLWFjOTE2ZTAxZDQ1YyIgc3RSZWY6ZG9jdW1lbnRJRD0idXVpZDoxRDIwNUFGNjZCRDlFNTExOUM5REMwMzg2RjlEQjFGNyIvPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDphYmMzNjIzMy1hOWNkLWNiNDQtODViYi0zZTgyMjEwYmIxMjYiIHN0RXZ0OndoZW49IjIwMTgtMDUtMDlUMTQ6NTE6MjUrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjAzN2ZiMGItNTU5Mi0xYjRkLWJjZGQtOWU4NGExMDJiMGM2IiBzdEV2dDp3aGVuPSIyMDE4LTA1LTA5VDE0OjUxOjI1KzA4OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2Vx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\"; function Sakura(x, y, s, r, fn) { this.x = x; this.y = y; this.s = s; this.r = r; this.fn = fn; } Sakura.prototype.draw = function (cxt) { cxt.save(); var xc = 5 * this.s / 5; cxt.translate(this.x, this.y); cxt.rotate(this.r); cxt.drawImage(img, 0, 0, 25 * this.s, 30 * this.s) cxt.restore(); } Sakura.prototype.update = function () { this.x = this.fn.x(this.x, this.y); this.y = this.fn.y(this.y, this.y); this.r = this.fn.r(this.r); if (this.x > window.innerWidth || this.x < 0 || this.y > window.innerHeight || this.y < 0) { this.r = getRandom('fnr'); if (Math.random() > 0.4) { this.x = getRandom('x'); this.y = 0; this.s = getRandom('s'); this.r = getRandom('r'); } else { this.x = window.innerWidth; this.y = getRandom('y'); this.s = getRandom('s'); this.r = getRandom('r'); } } } SakuraList = function () { this.list = []; } SakuraList.prototype.push = function (sakura) { this.list.push(sakura); } SakuraList.prototype.update = function () { for (var i = 0, len = this.list.length; i < len; i++) { this.list[i].update(); } } SakuraList.prototype.draw = function (cxt) { for (var i = 0, len = this.list.length; i < len; i++) { this.list[i].draw(cxt); } } SakuraList.prototype.get = function (i) { return this.list[i]; } SakuraList.prototype.size = function () { return this.list.length; } function getRandom(option) { var ret, random; switch (option) { case 'x': ret = Math.random() * window.innerWidth; break; case 'y': ret = Math.random() * window.innerHeight; break; case 's': ret = Math.random(); break; case 'r': ret = Math.random() * 6; break; case 'fnx': random = -0.5 + Math.random() * 1; ret = function (x, y) { return x + 0.5 * random - 1.7; }; break; case 'fny': random = 1.5 + Math.random() * 0.7 ret = function (x, y) { return y + random; }; break; case 'fnr': random = Math.random() * 0.03; ret = function (r) { return r + random; }; break; } return ret; } function startSakura() { requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame; var canvas = document.createElement('canvas'), cxt; staticx = true; canvas.height = window.innerHeight; canvas.width = window.innerWidth; canvas.setAttribute('style', 'position: fixed;left: 0;top: 0;pointer-events: none;'); canvas.setAttribute('id', 'canvas_sakura'); document.getElementsByTagName('body')[0].appendChild(canvas); cxt = canvas.getContext('2d'); var sakuraList = new SakuraList(); for (var i = 0; i < 50; i++) { var sakura, randomX, randomY, randomS, randomR, randomFnx, randomFny; randomX = getRandom('x'); randomY = getRandom('y'); randomR = getRandom('r'); randomS = getRandom('s'); randomFnx = getRandom('fnx'); randomFny = getRandom('fny'); randomFnR = getRandom('fnr'); sakura = new Sakura(randomX, randomY, randomS, randomR, {x: randomFnx, y: randomFny, r: randomFnR}); sakura.draw(cxt); sakuraList.push(sakura); } stop = requestAnimationFrame(function () { cxt.clearRect(0, 0, canvas.width, canvas.height); sakuraList.update(); sakuraList.draw(cxt); stop = requestAnimationFrame(arguments.callee); }) } window.onresize = function () { var canvasSnow = document.getElementById('canvas_snow'); } img.onload = function () { startSakura(); } function stopp() { if (staticx) { var child = document.getElementById(\"canvas_sakura\"); child.parentNode.removeChild(child); window.cancelAnimationFrame(stop); staticx = false; } else { startSakura(); } }"}],"posts":[{"title":"Java多线程情况下执行Shell命令","slug":"Java多线程情况下执行Shell命令","date":"2020-12-26T15:56:16.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/c4fc7445.html","link":"","permalink":"https://blog.hasaik.com/posts/c4fc7445.html","excerpt":"","text":"因为代码太多,所以我放入折叠框了,点击就能查看。相信各位都能看懂代码。 🔥 命令执行入口 IExecCommandServer 12345678910111213141516171819import java.util.List;/** * @author xuxu * @date 2020/12/23 12:17 */public interface IExecCommandServer { /** * 执行command * * @param job * @param cmd * @param dir * @return */ List<String> execCommand(ShellJob job, List<String> cmd, String dir);} 🔥 命令执行入口的实现 ExecCommandServerImpl 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.stereotype.Service;import java.io.*;import java.util.ArrayList;import java.util.List;/** * @author xuxu * @date 2020/12/23 12:18 */@Slf4j@Servicepublic class ExecCommandServerImpl implements IExecCommandServer { @Override public List<String> execCommand(ShellJob job, List<String> cmd, String dir) { Process process; try { log.info("当前执行cmd为:{}", cmd.toString()); ProcessBuilder processBuilder = new ProcessBuilder(cmd); // 设置此进程生成器的工作目录 if (StringUtils.isNotEmpty(dir)) { processBuilder.directory(new File(dir)); } processBuilder.redirectErrorStream(true); // 此进程生成器是否合并标准错误和标准输出 process = processBuilder.start(); if (job != null) { job.setProcess(process); } // 读流 List<String> lineString = readFromStream(process.getInputStream()); if (process.waitFor() == 0) { return lineString; } } catch (Exception e) { log.error("执行命令异常:{}", e); } return null; } /** * 从流中读取数据,防止进程僵死 * * @param inputStream */ private static List<String> readFromStream(InputStream inputStream) { List<String> lineList = new ArrayList<>(); InputStreamReader isr = null; BufferedReader br = null; try { isr = new InputStreamReader(inputStream); br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { if (StringUtils.isEmpty(line)) { try { log.info("读取输出只是为了防止进程僵死"); } catch (Exception ex) { log.error("读取shell输出流错误"); } } lineList.add(line); } return lineList; } catch (IOException e) { // do nothing,当进程被kill时,会报IOException } catch (Exception e) { log.error("读取shell输出流异常:{}", e); } finally { try { if (inputStream != null) { inputStream.close(); } if (isr != null) { isr.close(); } if (br != null) { br.close(); } } catch (Exception ex) { log.error("关闭输入流异常:{}", ex); } } return null; }} 🔥 利用以下实现每次Job调起都会向线程池提交一个对应的Job ShellJob 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106import com.scaffolding.example.utils.SpringUtils;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections.CollectionUtils;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicBoolean;/** * 可以是任何一个Job,每次Job调起都会向线程池提交一个对应的Job * * @author xuxu * @date 2020/12/26 10:22 */@Slf4j@Datapublic class ShellJob implements Runnable { private IExecCommandServer commandServer; /** * 设置进程生成器的工作目录 */ private final String workspace; /** * 执行的命令 */ private final List<String> cmd; /** * Job标识 */ private final Long jobId; /** * 终止线程使用 */ private volatile Process process; /** * 记录线程状态 */ private static AtomicBoolean killed = new AtomicBoolean(false); /** * 构造方法 * * @param workspace * @param cmd * @param jobId */ public ShellJob(String workspace, List<String> cmd, Long jobId) { this.workspace = workspace; this.cmd = cmd; this.jobId = jobId; this.commandServer = SpringUtils.getBean(IExecCommandServer.class); } @Override public void run() { log.info("Job开始执行,Job标识为:{}", jobId); Thread.currentThread().setName("shell-job-" + this.jobId); List<String> printString = new ArrayList<>(); try { printString = commandServer.execCommand(this, cmd, workspace); log.info("Job执行结束, 执行结果:{}", printString.toString()); } catch (Exception e) { log.info("Job执行异常:{}", e.toString() + ":" + e.getMessage()); JobManager.removeJob(this.getJobId()); // 回调的操作 callback("Fail"); } // 根据需求执行自己的逻辑 if (!killed.get()) { if (CollectionUtils.isNotEmpty(printString)) { // 回调的操作 callback("Success"); } else { // 回调的操作 callback("Fail"); } } // 无论执行失败还是成功都要移除Job JobManager.removeJob(this.getJobId()); } /** * 停止进程 */ public synchronized void stop() { killed.set(true); process.destroy(); JobManager.removeJob(this.getJobId()); } /** * 回调方法,可根据需求自行实现。 */ private void callback(String msg) { log.info("Job执行状态:{}", msg); // 可以写点回调的逻辑 }} 🔥 Job管理器 JobManager 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576import lombok.extern.slf4j.Slf4j;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * Job管理器 * * @author xuxu * @date 2020/12/26 10:55 */@Slf4jpublic class JobManager { /** * 记录Job */ private static final Map<Long, ShellJob> JOB_MAP = new ConcurrentHashMap<>(); /** * 添加Job * * @param job * @return */ public static boolean addJob(ShellJob job) { if (JOB_MAP.containsKey(job.getJobId())) { log.warn("添加Job失败, Job已经存在, Job标识:{}", job.getJobId()); return false; } log.info("添加Job成功, Job标识:{}", job.getJobId()); JOB_MAP.put(job.getJobId(), job); return true; } /** * 删除Job * * @param jobId * @return */ public static void removeJob(Long jobId) { if (!JOB_MAP.containsKey(jobId)) { log.warn("删除Job失败, Job不存在, Job标识:{}", jobId); return; } log.info("删除Job成功, Job标识:{}", jobId); JOB_MAP.remove(jobId); } /** * Kill Job * * @param jobId * @return */ public static boolean killJob(Long jobId) { log.info("开始终止Job, Job标识:{}", jobId); if (JOB_MAP.containsKey(jobId)) { JOB_MAP.get(jobId).stop(); log.info("终止Job成功, Job标识:{}", jobId); return true; } log.info("终止Job失败, Job不存在, Job标识:{}", jobId); return false; } /** * 停止所有Job */ public static void stopAll() { for (Map.Entry<Long, ShellJob> entry : JOB_MAP.entrySet()) { entry.getValue().stop(); } }} 🔥 测试 ExecCommandController 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutorService;import static java.util.concurrent.Executors.newFixedThreadPool;/** * @author xuxu * @date 2020/12/26 15:12 */@Slf4j@RestController@RequestMapping("/linux")public class ExecCommandController { private final static ExecutorService EXECUTE = newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); /** * 执行命令 */ @GetMapping("/exec") public void exec() { // 具体业务场景根据自己情况而定,我这里只举了个例子。 for (int j = 1; j <= 3; j++) { List<String> cmd = new ArrayList<>(); cmd.add("D:\\\\Software\\\\Git\\\\bin\\\\bash.exe"); cmd.add("-c"); cmd.add("curl -u admin:Xu19960211 -T \\"C:/test/1607428204749_" + j + ".tar\\" -X PUT \\"http://localhost:8081/artifactory/DEV/TEST/1607428204749_" + j + ".tar\\""); // 这里我用for循环的下标作为Job的标识 ShellJob job = new ShellJob(System.getProperty("user.dir"), cmd, (long) j); if (JobManager.addJob(job)) { // 执行线程 EXECUTE.execute(job); } } } /** * 停止执行命令 */ @GetMapping("/kill") public void kill(Long jobId) { boolean result = JobManager.killJob(jobId); if (result) { log.info("Kill job success"); } else { log.info("Kill job fail"); } }} 🔥 Spring工具类,方便在非spring管理环境中获取bean SpringUtils 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import org.springframework.beans.BeansException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * spring工具类 方便在非spring管理环境中获取bean * * @author xuxu * @date 2020/12/26 12:28 */@Componentpublic final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** * Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext = null; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringUtils.applicationContext == null) { SpringUtils.applicationContext = applicationContext; } } /** * 获取applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"},{"name":"Shell","slug":"Java/线程/Shell","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/Shell/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"},{"name":"Shell","slug":"Shell","permalink":"https://blog.hasaik.com/tags/Shell/"}]},{"title":"Java下载网络文件到指定目录","slug":"Java下载网络文件到指定目录","date":"2020-12-09T11:43:05.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/611a48d3.html","link":"","permalink":"https://blog.hasaik.com/posts/611a48d3.html","excerpt":"","text":"工具类代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990import lombok.extern.slf4j.Slf4j;import java.io.*;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;/** * @author xuxu * @date 2020/12/9 12:55 */@Slf4jpublic class FileUtil { /** * 说明:根据指定URL将文件下载到指定目标位置 * * @param urlPath 下载路径 * @param downloadDir 文件存放目录 * @return 返回下载文件 */ public static File downloadFile(String urlPath, String downloadDir) { File file = null; try { // 统一资源 URL url = new URL(urlPath); // 连接类的父类,抽象类 URLConnection urlConnection = url.openConnection(); // http的连接类 HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection; //设置超时 httpUrlConnection.setConnectTimeout(1000 * 5); //设置请求方式,默认是GET httpUrlConnection.setRequestMethod("GET"); // 设置字符编码 httpUrlConnection.setRequestProperty("Charset", "UTF-8"); // 打开到此 URL引用的资源的通信链接(如果尚未建立这样的连接)。 httpUrlConnection.connect(); // 文件大小 int fileLength = httpUrlConnection.getContentLength(); // 控制台打印文件大小 log.info("您要下载的文件大小为:" + fileLength / (1024 * 1024) + "MB"); // 建立链接从请求中获取数据 BufferedInputStream bin = new BufferedInputStream(httpUrlConnection.getInputStream()); // 指定文件名称(有需求可以自定义) String fileFullName = urlPath.substring(urlPath.lastIndexOf("/") + 1); // 指定存放位置(有需求可以自定义) File filePath = new File(downloadDir); // 是否是文件夹 if (!filePath.exists() && !filePath.isDirectory()) { log.info("目录不存在,创建目录:" + filePath); filePath.mkdir(); } file = new File(downloadDir + File.separatorChar + fileFullName); OutputStream out = new FileOutputStream(file); int size; int len = 0; byte[] buf = new byte[2048]; while ((size = bin.read(buf)) != -1) { len += size; out.write(buf, 0, size); } // 关闭资源 bin.close(); out.close(); log.info("文件下载成功!"); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); log.info("文件下载失败!"); } return file; } /** * 测试 * * @param args */ public static void main(String[] args) { // 指定资源地址,下载文件测试 File file = downloadFile("http://192.168.80.97:9090/artifactory/DEV/ipipe/pipeline/1607175965084_15.tar", "E:\\\\down"); System.out.println(file.getName()); }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"工具类","slug":"Java/工具类","permalink":"https://blog.hasaik.com/categories/Java/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}]},{"title":"基于HttpClient实现java中实现数据传输及文件传输","slug":"基于HttpClient实现java中实现数据传输及文件传输","date":"2020-12-09T10:56:53.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/b85c80d5.html","link":"","permalink":"https://blog.hasaik.com/posts/b85c80d5.html","excerpt":"","text":"🔥 在pom文件中引入相关jar 1234567891011121314151617181920<dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version></dependency><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8</version></dependency><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5</version></dependency><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5</version></dependency> 🔥 创建HttpClientRequestUtil工具类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210import net.sf.json.JSONObject;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.NameValuePair;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.HttpClient;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.ContentType;import org.apache.http.entity.StringEntity;import org.apache.http.entity.mime.HttpMultipartMode;import org.apache.http.entity.mime.MultipartEntityBuilder;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.impl.client.HttpClients;import org.apache.http.protocol.HTTP;import org.apache.http.util.EntityUtils;import org.springframework.web.multipart.MultipartFile;import java.io.*;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @author xuxu * @date 2020/12/9 10:31 */public class HttpClientRequestUtil { private static final HttpClient client = HttpClientBuilder.create().build(); /** * 使用POST请求 * * @param urlParameters * @param url * @return */ public static String doPost(List<NameValuePair> urlParameters, String url) { String result = ""; // 创建默认的httpClient实例. CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建httpPost HttpPost httppost = new HttpPost(url); RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3000) .setSocketTimeout(3000).setConnectionRequestTimeout(3000) .setRedirectsEnabled(true).build(); UrlEncodedFormEntity uefEntity; try { uefEntity = new UrlEncodedFormEntity(urlParameters, "UTF-8"); httppost.setEntity(uefEntity); httppost.setConfig(requestConfig); System.out.println("executing request " + httppost.getURI()); CloseableHttpResponse response = httpclient.execute(httppost); if (response.getStatusLine().getStatusCode() == 200) { try { HttpEntity entity = response.getEntity(); if (entity != null) { result = EntityUtils.toString(entity, "UTF-8"); System.out.println("--------------------------------------"); System.out.println("Response content: " + result); System.out.println("--------------------------------------"); } } finally { response.close(); } } else { System.out.println("******************************************请求失败+++++++++++++++++++++++++++++===="); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e1) { e1.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭连接,释放资源 try { httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } /** * GET请求方式 * * @param URL * @return */ public static String doGet(String URL) { try { HttpClient client = new DefaultHttpClient(); // 发送get请求 HttpGet request = new HttpGet(URL); HttpResponse response = client.execute(request); // 请求发送成功,并得到响应 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { // 读取服务器返回过来的json字符串数据 String strResult = EntityUtils.toString(response.getEntity()); return strResult; } } catch (IOException e) { e.printStackTrace(); } return null; } /** * POST请求application/json * * @param jsonStr * @param url * @return * @throws Exception */ public static String doPostJson(String jsonStr, String url) throws Exception { StringEntity sEntity = new StringEntity(JSONObject.fromObject(jsonStr).toString(), "utf-8"); sEntity.setContentType("application/json"); HttpPost post = new HttpPost(url); post.setEntity(sEntity); HttpResponse response = client.execute(post); HttpEntity entity = response.getEntity(); return EntityUtils.toString(entity, "UTF-8"); } /** * POST请求发送MultipartFile文件参数 * * @param url * @param multipartFiles * @param fileParName * @param params * @param headers * @param timeout * @return */ public static Map<String, String> doPostFile(String url, List<MultipartFile> multipartFiles, String fileParName, Map<String, Object> params, Map<String, String> headers, int timeout) { Map<String, String> resultMap = new HashMap<>(); CloseableHttpClient httpClient = HttpClients.createDefault(); String result; try { HttpPost httpPost = new HttpPost(url); // 设置请求头 for (Map.Entry<String, String> entry : headers.entrySet()) { if (entry.getValue() == null) { continue; } httpPost.setHeader(entry.getKey(), entry.getValue()); } MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setCharset(java.nio.charset.Charset.forName("UTF-8")); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); for (MultipartFile item : multipartFiles) { // 文件流 builder.addBinaryBody(fileParName, item.getInputStream(), ContentType.MULTIPART_FORM_DATA, item.getOriginalFilename()); } //解决中文乱码 ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8); for (Map.Entry<String, Object> entry : params.entrySet()) { if (entry.getValue() == null) { continue; } // 类似浏览器表单提交,对应input的name和value builder.addTextBody(entry.getKey(), entry.getValue().toString(), contentType); } HttpEntity entity = builder.build(); httpPost.setEntity(entity); // 执行提交 HttpResponse response = httpClient.execute(httpPost); // 设置连接超时时间 RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(timeout) .setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).build(); httpPost.setConfig(requestConfig); HttpEntity responseEntity = response.getEntity(); resultMap.put("code", String.valueOf(response.getStatusLine().getStatusCode())); resultMap.put("data", ""); if (responseEntity != null) { // 将响应内容转换为字符串 result = EntityUtils.toString(responseEntity, java.nio.charset.Charset.forName("UTF-8")); resultMap.put("data", result); } } catch (Exception e) { resultMap.put("code", "error"); resultMap.put("data", "HTTP请求出现异常: " + e.getMessage()); Writer w = new StringWriter(); e.printStackTrace(new PrintWriter(w)); } finally { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } return resultMap; }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"工具类","slug":"Java/工具类","permalink":"https://blog.hasaik.com/categories/Java/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}]},{"title":"Java代替if和switch的方法","slug":"Java代替if和switch的方法","date":"2020-11-12T16:39:35.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/bc6f4faf.html","link":"","permalink":"https://blog.hasaik.com/posts/bc6f4faf.html","excerpt":"","text":"🎉 公共接口 1234567891011121314package com.scaffolding.example.test;/** * 公共接口 * * @author xuxu * @date 2020/11/12 15:49 */public interface Function { /** * 要做的事情 */ void invoke();} 🎉 代替if else和switch的方法 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.scaffolding.example.test;import java.util.Map;/** * 代替'if else' 和 'switch'的方法 * * @author xuxu * @date 2020/11/12 15:49 */public class IfFunction<K> { private Map<K, Function> map; /** * 通过map类型来保存对应的条件key和方法 * * @param map a map */ public IfFunction(Map<K, Function> map) { this.map = map; } /** * 添加条件 * * @param key 需要验证的条件(key) * @param function 要执行的方法 * @return this. */ public IfFunction<K> add(K key, Function function) { this.map.put(key, function); return this; } /** * 确定key是否存在,如果存在,则执行相应的方法。 * * @param key the key need to verify */ public void doIf(K key) { if (this.map.containsKey(key)) { map.get(key).invoke(); } } /** * 确定key是否存在,如果存在,则执行相应的方法。 * 否则将执行默认方法 * * @param key 需要验证的条件(key) * @param defaultFunction 要执行的方法 */ public void doIfWithDefault(K key, Function defaultFunction) { if (this.map.containsKey(key)) { map.get(key).invoke(); } else { defaultFunction.invoke(); } }} 🎉 测试 1234567891011121314151617181920212223242526package com.scaffolding.example.test;import java.util.HashMap;/** * 测试 * * @author xuxu * @date 2020/11/12 15:51 */public class Test { public static void main(String[] args) { IfFunction<String> ifFunction = new IfFunction<>(new HashMap<>(5)); //定义好要判断的条件和对应执行的方法 ifFunction.add("1", () -> System.out.println("苹果")) .add("2", () -> System.out.println("西瓜")) .add("3", () -> System.out.println("橙子")); // 要进入的条件 ifFunction.doIf("1"); // 执行默认方法 ifFunction.doIfWithDefault("4", () -> System.out.println("默认方法")); }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"优化","slug":"Java/优化","permalink":"https://blog.hasaik.com/categories/Java/%E4%BC%98%E5%8C%96/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"优化","slug":"优化","permalink":"https://blog.hasaik.com/tags/%E4%BC%98%E5%8C%96/"}]},{"title":"Jenkins + Gitlab + Docker + Spring Boot 实现自动部署","slug":"Jenkins-Gitlab-Docker-SpringBoot实现自动部署","date":"2020-09-13T16:26:48.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/3fe685e0.html","link":"","permalink":"https://blog.hasaik.com/posts/3fe685e0.html","excerpt":"本文主要介绍持续集成的搭建方式,采用 Docker 的方式去搭建 Jenkins 环境,篇幅有点长,请仔细阅读。(无废话版)","text":"本文主要介绍持续集成的搭建方式,采用 Docker 的方式去搭建 Jenkins 环境,篇幅有点长,请仔细阅读。(无废话版) 推荐阅读本篇文章的注意事项篇节以后再进行搭建。 🔥 Docker安装 本文中我们使用 Centos7.x 进行 Docker 安装,所以我们需要在虚拟中先安装 Centos7,这一步请阅读者自行安装。 🎉 Docker安装步骤 1234567891011121314# 1. yum 包更新到最新。sudo yum update# 2. 安装需要的软件包,yum-util提供yun-config-manager功能,另外两个是devicemapper驱动依赖的。sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 3. 设置yum源为阿里云。sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# 4. 安装docker。sudo yum install docker-ce# 5. 安装完成后查看docker版本。docker -v 🎉 设置ustc的镜像(此处也可用阿里云免费镜像加速) ustc是老牌的linux镜像服务提供者了,还在遥远的ubuntu 5.04版本的时候就在用。ustc的docker镜像加速器速度很快。ustc docker mirror的优势之一就是不需要注册,是真正的公共服务。 ustc镜像帮助https://lug.ustc.edu.cn/wiki/mirrors/help/docker 编辑该文件(没有该文件则新建): 1vim /etc/docker/daemon.json 在该文件中输入如下内容: 123{"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]} 或阿里云的镜像加速(二选一) 123{"registry-mirrors": ["https://9cpn8tt6.mirror.aliyuncs.com"]} 🎉 Docker的启动与停止 1234567891011121314# 启动dockersystemctl start docker# 停止dockersystemctl stop docker# 重启dockersystemctl restart docker# 查看docker状态systemctl status docker# 开机启动dockersystemctl enable docker Docker的虚拟机常用命令https://www.cnblogs.com/ghostdot/p/12652820.html 🔥 Gitlab安装 GitLab是一个利用 Ruby on Rails 开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目安装。类似GitHub,能够浏览源代码,管理缺陷和注释,可以管理团队对仓库的访问。 🎉 查询并拉取镜像 1234# 查询gitlab镜像版本docker search gitlab# 拉取镜像docker pull gitlab/gitlab-ce:latest 🎉 创建GitLab的配置 (config) 、 日志 (log) 、数据 (data) 三个文件夹,放到容器之外, 便于日后升级, 因此请先准备这三个目录 123mkdir -p /apps/Devops/gitlab/configmkdir -p /apps/Devops/gitlab/logmkdir -p /apps/Devops/gitlab/data 🎉 运行GitLab容器 运行成功后,此时会在上面我们创建的目录中生成一些文件,后面需要修改文件。 12345678docker run --detach \\--publish 8443:443 --publish 8090:8090 --publish 2222:22 \\--name gitlab \\--restart always \\--volume /apps/Devops/gitlab/config:/etc/gitlab \\--volume /apps/Devops/gitlab/logs:/var/log/gitlab \\--volume /apps/Devops/gitlab/data:/var/opt/gitlab \\gitlab/gitlab-ce:latest 8090端口是页面访问端口。 【注意】:gitlab初次启动比较慢,耐心等待后再访问页面! 🎉 修改配置文件 修改 /apps/Devops/gitlab/config/gitlab.rb 1234进入文件后,把external_url改成部署机器的域名或者IP地址,并取消注释。vim /apps/Devops/gitlab/config/gitlab.rbexternal_url 'http://192.168.137.119:8090' #ip为部署机器的IP或域名 如图: 🎉 更改完配置文件运行以下命令重启容器 1docker restart gitlab 通过浏览器访问,默认账号root, 需要设置一个新密码。 访问报502说明容器还没启动完成,等待片刻即可访问到如下页面。 至此,Gitlab搭建完成。 🔥 Jenkins安装 🎉 拉取镜像 1docker pull jenkins/jenkins:lts 🎉 创建目录 由于防止jenkins中重要文件因为容器损毁或删除导致文件丢失,因此创建文件对外挂载。 1mkdir -p /apps/Devops/jenkins 并且需要对目录开放docker进程操作的完全读写的权限 1chmod 777 /apps/Devops/jenkins 🎉 启动容器 1docker run -itd -p 9980:8080 -p 50000:50000 --restart always -v /apps/Devops/jenkins:/var/jenkins_home --name jenkins jenkins/jenkins:lts -p 端口映射:Jenkins是Java程序,默认端口是8080 🎉 打开Jenkins管理页面 访问地址:http://192.168.137.119:9980/ 出现如上页面,代表jenkins启动成功。 🎉 查看日志获取初始密码 执行以下命令: 1docker logs -f jenkins 复制下图中红框内的初始密码。 当然,你也可以不通过日志查看,你可以进入黄色框中描述的文件查看初始密码也是一样的,二选一。 通过描述文件查看密码: 123456789# 1. 进入运行的jenkins容器中。docker exec -it jenkins /bin/bash# 2. 进入容器中的/var/jenkins_home/secrets目录,初始密码就在initialAdminPassword文件中。cd /var/jenkins_home/secretscat initialAdminPassword# 3. 退出容器exit 将密码复制、粘贴到如下框框中,进入jenkins,需要等待数十秒(可能更久)! 如果出现下图情况,等很久,还没有进入: 解决方案: (1)进入我们前面挂载的Jenkins目录 /apps/Devops/jenkins,修改文件 hudson.model.UpdateCenter.xml。 12cd /apps/Devops/jenkinsvim hudson.model.UpdateCenter.xml (2)将文件 hudson.model.UpdateCenter.xml 中 https://updates.jenkins.io/update-center.json 改成 http://updates.jenkins.io/update-center.json(把https改成http)。 (3)保存,重启Jenkins容器 1docker restart jenkins (4)重新进入Jenkins管理页面:http://192.168.137.119:9980/(稍等一会儿,就可以进入) 🎉 安装推荐的插件 如下图所示,左侧显示安装建议的插件。右侧选择自定义安装插件。 先按照建议插件进行安装,点击左侧即可。 如果全部都能正确安装,更好。出现安装失败的插件,等待所有结束,下方会有Retry可以进行重试。 最后重试后,依旧没有安装成功的,可以先continue,完成初始化的步骤。随后参考这篇文章解决。 安装完成后会自动出现如下界面: 将信息输入对应输入框内,点击保存并完成,之后的步骤默认点击保存并完成即可。 🎉 成功安装Jenkins 出现下图代表成功安装Jenkins: 安装成功之后重启一下jenkins容器: 1docker restart jenkins 🔥 Jenkins配置 🎉 设置Jenkins时区为北京时间 点击 Manage Jenkins(系统管理) ——> Script Console(脚本命令行) 输入脚本并运行: 1System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'Asia/Shanghai') 如图显示Result表示成功: 🎉 安装自动化构建和部署所需的插件 所需插件:Maven Integration、Pipeline Maven Integration、Gitlab、Gitlab hook、SSH、Publish Over SSH、Docker 点击 Manage Jenkins(系统管理) ——> Manage Plugins(插件管理) 🐳 安装Maven插件 点击可选插件 ——> 过滤Maven Integration插件 ——> 勾选Maven Integration和Pipeline Maven Integration ——> 点击直接安装 如图开始安装插件: 安装完成后,即可在插件管理下的已安装选项卡下看到刚刚已经安装的插件。 🐳 安装Gitlab插件 点击可选插件 ——> 过滤Gitlab插件 ——> 勾选Gitlab和Gitlab Hook ——> 点击直接安装 🐳 安装SSH插件和Publish Over SSH插件 安装Publish Over SSH插件的原因:因为本方式是使用docker启动jenkins服务,所以在jenkins后续执行构建任务时候,需要在build成功后,将服务的jar包(以spring boot)服务为例,需要将jar包拷贝到Dockerfile所在服务器的指定目录,进行微服务的启动;所以,此处需要配置SSH服务器的连接,意思就是在jenkins的任务结束后,去执行指定的服务器上的shell命令,做spring boot或cloud服务的镜像的构建,容器的运行,等一系列的事情。 点击可选插件 ——> 过滤SSH插件 ——> 勾选SSH和Publish Over SSH ——> 点击直接安装 🐳 安装Docker插件 点击可选插件 ——> 过滤Docker插件 ——> 勾选Docker ——> 点击直接安装 插件全部安装完成后,可以重启一下Jenkins。 🎉 添加凭据 点击 Manage Jenkins(系统管理) ——> Manage Credentials(凭据管理) 点击添加凭据 ——> 输入宿主机服务器的用户名和密码等信息并保存 🎉 配置SSH remote hosts 这个配置是干什么的呢?配置SSH连接Dockerfile所在服务器的相关信息,并添加凭证,最后测试连接并保存,以备后面使用!!! 点击 Manage Jenkins(系统管理) ——> 系统配置 找到配置 ——> 下拉选择SSH remote hosts 如下图,输入对应的信息,并校验是否连接成功!成功后,点击应用 ——> 点击保存 🎉 配置Publish over SSH 找到配置 ——> 下拉选择SSH remote hosts 进行相关配置即可。 🎉 全局工具配置 由于我们要实现的是SpringBoot项目的自动化部署操作,所以需要安装JDK、Git、Maven、Docker。 🐳 安装JDK 可以安装多个,根据项目JDK版本需求。 输入自定义JDK名称 ——> 勾选自动安装 ——> 输入Oracle账户、密码 ——> 选择JDK版本 ——> 勾选同意协议 🐳 安装Git 输入自定义Git名称 ——> 勾选自动安装 🐳 安装Maven 输入自定义名称 ——> 勾选自动安装 ——> 选择版本 🐳 安装Docker 输入自定义名称 ——> 勾选自动安装 最后,点击应用 ——> 点击保存即可。 🔥 新建Jenkins任务 🎉 点击新建任务,输入名称 注意:本名称一般和项目名称一致,因为本名称会在jenkins工作空间下生成目录,类似于IDEA或Eclipse的工作空间的概念。所以,一般情况下,保证本名称=项目名称=docker镜像名称=docker容器名称 这样能尽可能的减轻jenkins配置的shell命令的复杂性!(空间命名要小写:因为镜像名不允许存在大写字母) 选择构建一个Maven项目(因为是Spring Boot的服务) 🎉 源码管理 输入描述信息,源码管理选择Git,从gitlab复制克隆地址粘贴到Repository URL中,没有报错就表示OK的,(注意,这里我是克隆HTTP方式的地址,如果你是克隆SSH方式的地址,你需要添加Credentials,配置一下就可以了) 🎉 构建触发器 接下来将会生成供gitlab配置webhook使用的URL和Token,请记录下来,后面会使用。 点击高级,拉下来找到Generate并点击,生成一串Secret Token。 🎉 添加webhook 前往gitlab,进入要构建的项目,在setting中选择Webhooks,输入URL和Secret Token 这两在上面图中已经给你标注了,去掉Enable SSL verification的勾选。 点击Add webhook,如图表示成功添加了webhook: 如果添加不成功解决方案请参考:解决 Url is blocked: Requests to the local network are not allowed 🎉 构建环境 勾选Add timestamps to the Console Output,等下可以看到控制台打印的信息,这个根据自己的需求勾选。 🎉 Pre Steps(构建之前的步骤) 配置前一步需要做的事情是:清理本项目在jenkins的workspace中的历史文件夹。 你可以不用知道WORKSPACE具体的地址在哪里,因为下方有链接可以查看到当前jenkins中有哪些可用的变量供你使用。 默认WORKSPACE地址:/var/jenkins_home/workspace(如果你jenkins是docker启动的,并且挂载了目录在宿主机,那你在宿主机也是可以看到的,即 /apps/Devops/jenkins/workspace) 本处选择的是执行shell,则表示本处配置的shell命令,是默认在jenkins容器中执行的,而不是在宿主机上。 下拉选择执行 shell: 在执行shell的命令中输入以下命令,设置全局变量: 123456SERVER_NAME_1=jenkins-docker-gitlab-springbootecho "=========================>>>>>>>工作空间WORKSPACE的地址:$WORKSPACE "cd $WORKSPACEecho "=========================>>>>>>>进入工作空间WORKSPACE,清除工作空间中原项目的工作空间$SERVER_NAME_1 "rm -rf $SERVER_NAME_1echo "=========================>>>>>>>清除工作空间中原项目的工作空间$SERVER_NAME_1 ......成功success" 注意:本处的SERVER_NAME_1=jenkins-docker-gitlab-springboot是配置项目的名称 🎉 Build(构建) 我们是SpringBoot项目,所以用到maven,这里设置一下全局操作,clean项目,并打成jar包,所以这里输入:clean package 🎉 Post Steps(执行任务) 只在jenkins构建成功后,才执行这一步。 因为最后的构建成功的maven项目的jar包是以docker启动服务为目的,所以最后的docker操作,一定是在jenkins容器以外的服务器上运行的,可能是本机宿主机,也可能是远程的服务器,这个根据自己的情况去配置。 本处选择,在远程的SSH执行shell脚本。 选中只有构建成功才执行这些命令,然后选择Execute shell script on remote host using ssh。 shell命令 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132#=====================================================================================#=================================定义初始化变量======================================#=====================================================================================#操作/项目路径(Dockerfile存放的路劲)BASE_PATH=/apps/jenkins-docker-gitlab-springboot# jenkins构建好的源jar路径 SOURCE_PATH=/apps/Devops/jenkins/workspace#【docker 镜像】【docker容器】【Dockerfile同目录下的jar名字[用它build生成image的jar]】【jenkins的workspace下的项目名称】#这里都以这个命名[微服务的话,每个服务都以 ms-项目名 这种格式命名]#注意统一名称!!!!!SERVER_NAME=jenkins-docker-gitlab-springboot#容器id [grep -w 全量匹配容器名] [awk 获取信息行的第一列,即容器ID] [无论容器启动与否,都获取到]CID=$(docker ps -a | grep -w "$SERVER_NAME" | awk '{print $1}')#镜像id [grep -w 全量匹配镜像名] [awk 获取信息行的第三列,即镜像ID]IID=$(docker images | grep -w "$SERVER_NAME" | awk '{print $3}')#源jar完整地址 [jenkins构建成功后,会在自己的workspace/项目/target 下生成maven构建成功的jar包,获取jar包名的完整路径]#例如:/apps/Devops/jenkins/workspace/jenkins-docker-gitlab-springboot/target/jenkins-docker-gitlab-springboot-0.0.1-SNAPSHOT.jarSOURCE_JAR_PATH=$(find "$SOURCE_PATH/$SERVER_NAME/target/" -name "*$SERVER_NAME*.jar" )DATE=`date +%Y%m%d%H%M%S`#=====================================================================================#============================对原本已存在的jar进行备份================================#=====================================================================================# 备份function backup(){ if [ -f "$BASE_PATH/$SERVER_NAME.jar" ]; then echo "=========================>>>>>>>$SERVER_NAME.jar 备份..." mv $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar echo "=========================>>>>>>>备份老的 $SERVER_NAME.jar 完成" else echo "=========================>>>>>>>老的$BASE_PATH/$SERVER_NAME.jar不存在,跳过备份" fi}#=====================================================================================#=========================移动最新源jar包到Dockerfile所在目录=========================#===================================================================================== # 查找源jar文件名,进行重命名,最后将源文件移动到Dockerfile文件所在目录function transfer(){ echo "=========================>>>>>>>源文件完整地址为 $SOURCE_JAR_PATH" echo "=========================>>>>>>>重命名源文件" mv $SOURCE_JAR_PATH $SOURCE_PATH/$SERVER_NAME/target/$SERVER_NAME.jar echo "=========================>>>>>>>最新构建代码 $SOURCE_PATH/$SERVER_NAME/target/$SERVER_NAME.jar 迁移至 $BASE_PATH" cp $SOURCE_PATH/$SERVER_NAME/target/$SERVER_NAME.jar $BASE_PATH echo "=========================>>>>>>>迁移完成Success"} #=====================================================================================#==================================构建最新镜像=======================================#===================================================================================== # 构建docker镜像function build(){ #无论镜像存在与否,都停止原容器服务,并移除原容器服务 echo "=========================>>>>>>>停止$SERVER_NAME容器,CID=$CID" docker stop $CID echo "=========================>>>>>>>移除$SERVER_NAME容器,CID=$CID" docker rm $CID #无论如何,都去构建新的镜像 if [ -n "$IID" ]; then echo "=========================>>>>>>>存在$SERVER_NAME镜像,IID=$IID" echo "=========================>>>>>>>移除老的$SERVER_NAME镜像,IID=$IID" docker rmi $IID echo "=========================>>>>>>>构建新的$SERVER_NAME镜像,开始---->" cd $BASE_PATH docker build -t $SERVER_NAME . echo "=========================>>>>>>>构建新的$SERVER_NAME镜像,完成---->" else echo "=========================>>>>>>>不存在$SERVER_NAME镜像,构建新的镜像,开始--->" cd $BASE_PATH docker build -t $SERVER_NAME . echo "=========================>>>>>>>构建新的$SERVER_NAME镜像,结束--->" fi} #=====================================================================================#==============================运行docker容器,启动服务===============================#=====================================================================================# 运行docker容器function run(){ backup transfer build docker run --name $SERVER_NAME -itd --net=host -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro $SERVER_NAME } #入口run OK,到这里基本的任务已经新建成功,至于后续的两个步骤,根据自己的需求自行配置,没有难度的。 点击应用,保存。 🎉 测试 测试push事件触发自动化构建和部署,点击test下拉选择push events,出现HTTP 200表示OK了。 回到Jenkins可以看到任务列表,查看构建信息等。 待Jenkins构建成功之后,在服务器上执行命令:docker ps,可以看到我们启动起来的 SpringBoot 容器: 在浏览器输入:http://服务器ip:端口/ 即可访问刚自动部署的项目: 🔥 配置邮件通知 完成基于jenkins的持续集成部署后,任务构建执行完成,测试结果需要通知到相关人员(这个邮件通知根据自己需求选择添加,非必须)。 🎉 安装邮件插件 由于Jenkins自带的邮件功能比较鸡肋,因此这里推荐安装专门的邮件插件,不过下面也会顺带介绍如何配置Jenkins自带的邮件功能作用。 点击系统管理 ——> 插件管理 ——> 可选插件: 选择Email Extension Plugin插件进行安装,安装好之后重启Jenkins。 🎉 系统设置 点击系统管理 ——> 系统配置,进行邮件配置: 🐳 设置Jenkins地址和管理员邮箱地址 🐳 设置发件人等信息 这里的发件人邮箱地址切记要和系统管理员邮件地址保持一致(当然,也可以设置专门的发件人邮箱,不过不影响使用,根据具体情况设置即可) 🐳 配置邮件内容模版 邮箱内容模版(Default Content) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title> </head> <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4" offset="0"> <table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> <tr> 本邮件由系统自动发出,无需回复!<br/> 各位同事,大家好,以下为${PROJECT_NAME}项目构建信息</br> <td><font color="#CC0000">构建结果 - ${BUILD_STATUS}</font></td> </tr> <tr> <td><br /> <b><font color="#0B610B">构建信息</font></b> <hr size="2" width="100%" align="center" /></td> </tr> <tr> <td> <ul> <li>项目名称 : ${PROJECT_NAME}</li> <li>构建编号 : 第${BUILD_NUMBER}次构建</li> <li>触发原因: ${CAUSE}</li> <li>构建状态: ${BUILD_STATUS}</li> <li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li> <li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li> <li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li> <li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li> </ul> <h4><font color="#0B610B">失败用例</font></h4><hr size="2" width="100%" />$FAILED_TESTS<br/><h4><font color="#0B610B">最近提交(#$SVN_REVISION)</font></h4><hr size="2" width="100%" /><ul>${CHANGES_SINCE_LAST_SUCCESS, reverse=true, format="%c", changesFormat="<li>%d [%a] %m</li>"}</ul>详细提交: <a href="${PROJECT_URL}changes">${PROJECT_URL}changes</a><br/> </td> </tr> </table> </body> </html> 🐳 设置邮件触发机制 上面的几步完成后,点击应用,保存即可。 🐳 配置Jenkins自带的邮件功能 配置内容如下,和Email Extension Plugin插件同样的配置,可以通过勾选通过发送测试邮件测试配置按钮来测试配置是否成功发送邮件,如下图: 完成上面的系统设置后,点击应用保存即可。 🎉 项目配置 在完成系统设置后,还需要给需要构建的项目进行邮件配置。 🐳 进入项目配置界面 进入新建的项目界面,点击配置按钮,进入系统配置页面。 🐳 配置构建设置模块 点击上方的构建设置选项,配置内容如下: 🐳 配置构建后操作模块 点击上方的构建后操作选项,添加构建后操作步骤 Editable Email Notification,配置内容如下: 接上图: 配置内容默认即可,邮件内容类型可以根据自己的配置选择,收件人列表可以从前面的系统设置中默认收件人选项配置。 🎉 构建触发邮件测试 如下图,为我收到的测试邮件,邮件内容可以通过系统设置里面进行个性化的配置,可参考我上面的模板,或者自定义即可。 🔥 注意 (1)shell脚本中需要的文件夹没有则自建(不用完全跟我一样)。 (2)Dockerfile需和jar包放到同一目录下,附上我使用的Dockerfile文件内容: 1234FROM jdk1.8MAINTAINER xuxuCOPY jenkins-docker-gitlab-springboot.jar jenkins-docker-gitlab-springboot.jarCMD ["java","-jar","jenkins-docker-gitlab-springboot.jar"] (3)如果没有基础 JDK 镜像可参照 这篇文章 完成。 (4)构建过程中可能提示没有权限操作文件夹,按照下面执行解决即可: 1234567891011121314151617181920212223242526# 1. 查看所有容器docker ps -a# 2. 进入jenkins容器内部docker exec -it jenkins /bin/bash# 3. 查看当前操作用户是否是jenkinswhoami# 4. 推出exit# 5. 以root用户进入jenkins容器内部docker exec -it -u root jenkins /bin/bash# 6. 切换到下面目录cd /var/jenkins_home# 7. 如果workspace可操作的用户不是jenkins用户需添加jenkins用户操作权限ls -all# 8. 添加jenkins用户操作权限chown -R jenkins workspace# 9. 添加目录操作权限chmod 777 workspace 🔥 总结 经过我们多款软件的安装配置,我们逐步掌握了如何上床Spring Boot项目到Gitlab中,并使用Jenkins自动构建任务,另外依托于Docker,让这一切变得更加方便,希望大家都多多思考,让机器自动干活,减少我们IT从业人员的重复、繁琐的工作量。","categories":[{"name":"自动部署","slug":"自动部署","permalink":"https://blog.hasaik.com/categories/%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2/"}],"tags":[{"name":"Gitlab","slug":"Gitlab","permalink":"https://blog.hasaik.com/tags/Gitlab/"},{"name":"Jenkins","slug":"Jenkins","permalink":"https://blog.hasaik.com/tags/Jenkins/"},{"name":"Docker","slug":"Docker","permalink":"https://blog.hasaik.com/tags/Docker/"}]},{"title":"CyclicBarrier 的理解和使用","slug":"CyclicBarrier的理解和使用","date":"2020-08-31T09:36:48.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/6d42a1fc.html","link":"","permalink":"https://blog.hasaik.com/posts/6d42a1fc.html","excerpt":"","text":"🔥 CyclicBarrier 的理解 CyclicBarrier 是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier 。 举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier 。 🔥 CyclicBarrier 的方法说明 🎉 构造方法 CyclicBarrier(int parties):创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。 CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。 🎉 方法 int await() 等待所有 parties 已经在这个障碍上调用了 await 。 int await(long timeout, TimeUnit unit) 等待所有 parties 已经在此屏障上调用 await ,或指定的等待时间过去。 int getNumberWaiting() 返回目前在屏障处等待的线程个数。 int getParties() 返回履行这个障碍所需的 parties 数量。 boolean isBroken() 查询这个障碍是否处于破碎状态。 void reset() 将屏障重置为初始状态。 🔥 CyclicBarrier 的用法 CyclicBarrier 核心主要有两点:线程组内彼此相互等待,然后大家开始做某件事;这一代结束后开始下一代–重用思想。 CountDownLatch 核心思想为确保某些活动直到其他活动都完成才继续执行,而且 CountDownLatch 不可重用。 举例,班级集体野炊,在大巴上等待所有同学到来才开始出发,一个班级集合完毕出发一辆大巴,这是 CyclicBarrier 。到达目的地后需要同学自行寻找食材,寻找到需要的所有食材后才开始做饭,一个班级做饭活动是一次性的,这是 CountDownLatch 。 🎉 典型用法1 赛跑时,等待所有人都准备好时,才起跑。 代码示例: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455import lombok.extern.slf4j.Slf4j;import java.util.Random;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.ExecutorService;import static java.util.concurrent.Executors.*;/** * @author: Xuxu * @date: 2020-08-31 10:48 **/@Slf4jpublic class CyclicBarrierTest { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(3); ExecutorService executorService = newFixedThreadPool(3); for (int i = 1; i <= 3; i++) { executorService.submit(new Thread(new Runner(cyclicBarrier, i + "号选手"))); } executorService.shutdown(); } static class Runner implements Runnable { /** * 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point) */ private CyclicBarrier barrier; private String name; Runner(CyclicBarrier barrier, String name) { super(); this.barrier = barrier; this.name = name; } @Override public void run() { try { Thread.sleep(1000 * (new Random()).nextInt(8)); System.out.println(name + " 准备好了..."); // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println(name + " 起跑!"); } }} 运行结果: 1234561号选手 准备好了...3号选手 准备好了...2号选手 准备好了...2号选手 起跑!1号选手 起跑!3号选手 起跑! 🎉 典型用法2 CyclicBarrier 等待与复用:宿舍四哥们约着去操场打球 代码示例: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364import java.util.concurrent.*;/** * @author: Xuxu * @date: 2020-08-31 11:31 **/public class CyclicBarrierTest1 { private static final ExecutorService executorService = Executors.newFixedThreadPool(4); /** * 当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。 */ private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场") ); private static class GoThread extends Thread { private final String name; GoThread(String name) { this.name = name; } @Override public void run() { System.out.println(name + "开始从宿舍出发"); try { cyclicBarrier.await();//拦截线程 System.out.println(name + "从楼底下出发"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args) { // TODO Auto-generated method stub String[] str = {"李明", "王强", "刘凯", "赵杰"}; String[] str1 = {"王二", "洪光", "雷兵", "赵三"}; for (int i = 0; i < 4; i++) { executorService.execute(new GoThread(str[i])); } try { Thread.sleep(4000); System.out.println("四个人一起到达球场,现在开始打球"); System.out.println("现在对CyclicBarrier进行复用....."); System.out.println("又来了一拨人,看看愿不愿意一起打:"); } catch (InterruptedException e) { e.printStackTrace(); } cyclicBarrier.reset(); //进行复用: for (int i = 0; i < 4; i++) { executorService.execute(new GoThread(str1[i])); } try { Thread.sleep(4000); System.out.println("四个人一起到达球场,表示愿意一起打球,现在八个人开始打球"); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown(); }} 运行结果: 12345678910111213141516171819202122刘凯开始从宿舍出发赵杰开始从宿舍出发王强开始从宿舍出发李明开始从宿舍出发寝室四兄弟一起出发去球场李明从楼底下出发刘凯从楼底下出发赵杰从楼底下出发王强从楼底下出发四个人一起到达球场,现在开始打球现在对CyclicBarrier进行复用.....又来了一拨人,看看愿不愿意一起打:王二开始从宿舍出发洪光开始从宿舍出发雷兵开始从宿舍出发赵三开始从宿舍出发寝室四兄弟一起出发去球场赵三从楼底下出发王二从楼底下出发洪光从楼底下出发雷兵从楼底下出发四个人一起到达球场,表示愿意一起打球,现在八个人开始打球 🔥 CountDownLatch 和 CyclicBarrier 的比较 CountDownLatch 是线程组之间的等待,即一个(或多个)线程等待N个线程完成某件事情之后再执行;而 CyclicBarrier 则是线程组内的等待,即每个线程相互等待,即N个线程都被拦截之后,然后依次执行。 CountDownLatch 是减计数方式,而 CyclicBarrier 是加计数方式。 CountDownLatch 计数为0无法重置,而 CyclicBarrier 计数达到初始值,则可以重置。 CountDownLatch 不可以复用,而 CyclicBarrier 可以复用。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"}]},{"title":"Java 垃圾收集算法","slug":"Java垃圾收集算法","date":"2020-08-24T19:52:59.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/2375015f.html","link":"","permalink":"https://blog.hasaik.com/posts/2375015f.html","excerpt":"","text":"JVM 中的垃圾回收算法有标记-清除算法、复制算法、标记-整理算法、分代收集算法四种算法。 🔥 标记 - 清除算法 标记-清除(Mark-Sweep)算法是现代垃圾回收算法的思想基础,它将垃圾回收分为「标记」和「清除」两个阶段: 在标记阶段,通过 「可达性分析算法」标记出所有需要回收的对象 在清除阶段,清除所有被标记为可以回收的对象。 算法示意图如下: 这个算法的主要缺点有两个: 效率问题,标记和清除两个过程的效率都不高 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 后续的很多垃圾收集算法都是基于此算法的思路进行改进而得到的。垃圾收集器中的 CMS 是基于标记-清除算法实现的,不过这种收集器也逐渐被取代了。 🔥 标记 - 整理算法 标记-整理(Mark-Compact)算法,类似于标记-清除算法,不过它标记完对象后,不是直接对可回收对象进行清除;而是让所有仍会存活的对象都向一端移动,然后直接清理掉边界以外的内存。算法示意如下图所示: 相对于标记-清除算法,标记-整理算法的优点是解决了内存空间碎片的问题,使对象创建时的内存分配更快速了(也可以使用 TLAB 进行分配);缺点还是效率问题,标记和整理这两个过程的效率都不高。 基于标记-整理算法实现的垃圾收集器有很多,如 Serial 收集器、ParNew 收集器、Parallel Old 收集器、G1 收集器等。 🔥 复制算法 复制(copying)算法的思路为:它将内存按容量分为大小相等的两块,每次只使用其中的一块。当正在使用的内存块 A 的内存用完了,就将还活着的对象复制到另一块内存 B 上面,然后再把之前使用的内存块 A 的空间一次性清理掉,就这样循环往复。 这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可。复制算法的优点是解决了效率问题和内存碎片的问题,算法示意图如下: 由此可见,复制算法的缺点就是每次都会浪费掉一半的内存空间,而且,在对象存活率较高时就要进行较多的复制操作,效率会变低。 现在很多垃圾收集器都采用复制算法来回收新生代。 🔥 分代收集算法 分代收集(Generational Collection)算法并没有什么新的垃圾回收思想,它只是根据对象存活周期的不同将内存划分为几块(一般是把 Java 堆划分为新生代和老年代),然后根据各个块的特点采用最适当的垃圾收集算法。 算法示意如图(其中,Eden、Survivor From 和 Survivor To 空间均属于新生代(Young),Old 空间属于老年代,一般这几个内存空间的大小比例为:Eden : Survivor From : Survivor To = 8 : 1 : 1 、Young : Old = 1 : 2 ): 如上图所示,分代收集算法的策略为: 在新生代中,每次垃圾收集都有都发现有大批对象死去,只有少量存活,就选用复制算法。将 Eden 和 Survivor From 空间中还存活着的对象一次性复制到 Survivor To 空间上;当 Survivor To 空间不够用时,需要依赖其它内存(这里指老年代)来进行分配担保(Handle Promotion);最后对刚才用过的 Eden 和 Survivor From 空间进行清理,清理之后 Survivor 区的 From 和 To 进行角色交换,之前的 From 变成了 To,之前的 To 变成了 From,也就是说无论如何都要保证名为 To 的 Survivor 区域是空的。 在老年代中,因为对象存活率高,也没有额外空间对它进行分配担保,就必须使用「标记-清除」或者「标记-整理」算法来进行回收。 当前的商业虚拟机基本都是采用的分代收集算法。 🔥 附:对象的一辈子理解 我是一个普通的 Java 对象,我出生在 Eden 区,在 Eden 区我还看到和我长得很像的小兄弟,我们在 Eden 区中玩了挺长时间。有一天 Eden 区中的人实在太多了,我就被迫去了 Survivor 区的 From 区,自从去了 Survivor 区,我就开始飘了,有时候在 Survivor 区的 From 区,有时候在 Survivor 区的 To 区,居无定所,直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了老年代那边,老年代里,人很多,并且年龄都挺大的,我这里也认识了很多人。在老年代里,我生活了20年(每次GC加一岁),然后被回收。 新对象申请内存空间流程图如下: 如有不正,欢迎指出。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"JVM","slug":"Java/JVM","permalink":"https://blog.hasaik.com/categories/Java/JVM/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"JVM","slug":"JVM","permalink":"https://blog.hasaik.com/tags/JVM/"}]},{"title":"CountDownLatch 的理解和使用","slug":"CountDownLatch的理解和使用","date":"2020-08-21T10:57:48.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/cf9c1454.html","link":"","permalink":"https://blog.hasaik.com/posts/cf9c1454.html","excerpt":"最近在看多线程之间的通信,笔者我觉得自己的脑袋实在不够用了,很多东西看过就忘,所以今天抽出点时间码一遍 CountDownLatch 的理解和使用。 此文也是从网上查找的资料,然后自己敲一遍,以便加深印象和更深入的理解。","text":"最近在看多线程之间的通信,笔者我觉得自己的脑袋实在不够用了,很多东西看过就忘,所以今天抽出点时间码一遍 CountDownLatch 的理解和使用。 此文也是从网上查找的资料,然后自己敲一遍,以便加深印象和更深入的理解。 🔥 CountDownLatch 的概念 CountDownLatch 是一个同步工具类,用来协调多个线程之间的同步,或者说其为线程之间的通信(而不是用作互斥作用)。 CountDownLatch 能够使一个线程在等待另外一些线程完成各自的工作之后,再继续执行。通过一个计数器来进行实现,计数器的初始值为线程的数量,当每一个线程完成自己的任务后,计数器的值就会减一,当计数器的值为零时,表示所有线程都已经完成相应的任务,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。 🔥 CountDownLatch 的方法说明 public void countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。如果当前计数等于零,则不发生任何操作。 public boolean await (long timeout, TimeUnit unit) throws InterruptedException 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。 如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态: 如果计数到达零,则该方法返回 true 值。 如果当前线程,在进入此方法时已经设置了该线程的中断状态;或者在等待时被中断,则抛出 InterruptedException,并且清除当前线程的已中断状态。 如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。 参数: timeout - 要等待的最长时间 unit - timeout 参数的时间单位 返回: 如果计数到达零,则返回true;如果在计数到达零之前超过了等待时间,则返回false。 抛出: InterruptedException - 如果当前线程在等待时被中断则抛出 InterruptedException 异常 🔥 CountDownLatch 的用法 🎉 典型用法1 某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在 CountDownLatch 上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 代码示例: 12345678910111213141516171819202122232425262728293031323334353637383940import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import static java.lang.Math.*;import static java.util.concurrent.Executors.newFixedThreadPool;/** * @author: Xuxu * @date: 2020-08-21 14:51 **/public class CountdownLatchTest1 { public static void main(String[] args) { ExecutorService service = newFixedThreadPool(3); final CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { Runnable runnable = () -> { try { System.out.println("子线程" + Thread.currentThread().getName() + "开始执行"); Thread.sleep((long) (random() * 10000)); System.out.println("子线程" + Thread.currentThread().getName() + "执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 很关键, 无论上面程序是否异常必须执行countDown,否则await无法释放 latch.countDown(); } }; service.execute(runnable); } try { System.out.println("主线程" + Thread.currentThread().getName() + "等待子线程执行完成..."); latch.await();//阻塞当前线程,直到计数器的值为0 System.out.println("主线程" + Thread.currentThread().getName() + "开始执行..."); } catch (InterruptedException e) { e.printStackTrace(); } service.shutdown(); }} 运行结果: 12345678主线程main等待子线程执行完成...子线程pool-1-thread-3开始执行子线程pool-1-thread-1开始执行子线程pool-1-thread-2开始执行子线程pool-1-thread-2执行完成子线程pool-1-thread-1执行完成子线程pool-1-thread-3执行完成主线程main开始执行... 🎉 典型用法2 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。 代码示例: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import static java.lang.Math.*;import static java.util.concurrent.Executors.*;/** * @author: Xuxu * @date: 2020-08-21 15:15 **/public class CountdownLatchTest2 { public static void main(String[] args) { ExecutorService service = newCachedThreadPool(); final CountDownLatch cdOrder = new CountDownLatch(1); final CountDownLatch cdAnswer = new CountDownLatch(4); for (int i = 0; i < 4; i++) { Runnable runnable = () -> { try { System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令"); cdOrder.await(); System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令"); Thread.sleep((long) (random() * 10000)); System.out.println("选手" + Thread.currentThread().getName() + "到达终点"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 很关键, 无论上面程序是否异常必须执行countDown,否则await无法释放 cdAnswer.countDown(); } }; service.execute(runnable); } try { Thread.sleep((long) (Math.random() * 10000)); System.out.println("裁判" + Thread.currentThread().getName() + "即将发布口令"); cdOrder.countDown(); System.out.println("裁判" + Thread.currentThread().getName() + "已发送口令,正在等待所有选手到达终点"); cdAnswer.await(); System.out.println("所有选手都到达终点"); System.out.println("裁判" + Thread.currentThread().getName() + "汇总成绩排名"); } catch (InterruptedException e) { e.printStackTrace(); } service.shutdown(); }} 运行结果: 12345678910111213141516选手pool-1-thread-2正在等待裁判发布口令选手pool-1-thread-3正在等待裁判发布口令选手pool-1-thread-4正在等待裁判发布口令选手pool-1-thread-1正在等待裁判发布口令裁判main即将发布口令裁判main已发送口令,正在等待所有选手到达终点选手pool-1-thread-3已接受裁判口令选手pool-1-thread-1已接受裁判口令选手pool-1-thread-4已接受裁判口令选手pool-1-thread-2已接受裁判口令选手pool-1-thread-2到达终点选手pool-1-thread-1到达终点选手pool-1-thread-4到达终点选手pool-1-thread-3到达终点所有选手都到达终点裁判main汇总成绩排名","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"},{"name":"通信","slug":"通信","permalink":"https://blog.hasaik.com/tags/%E9%80%9A%E4%BF%A1/"}]},{"title":"Java 多线程的实现","slug":"Java多线程的实现","date":"2020-08-10T09:35:37.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/f48050ed.html","link":"","permalink":"https://blog.hasaik.com/posts/f48050ed.html","excerpt":"","text":"🔥 进程和线程之间的关系 线程是在进程基础之上创建并使用的更小的程序单元,所以线程依赖于进程的支持。线程的启动速度要比进程快上很多,高并发处理的时候,线程的性能要高于进程。 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。 线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。 🔥 多线程实现的四种方式 继承Thread类,重写run方法。 实现Runnable接口,重写run方法。 通过Callable和FutureTask创建线程 通过线程池创建线程 🔥 代码示例 🎉 继承Thread类 123456789101112131415161718/** * @author: Xuxu * @date: 2020-08-10 10:00 **/public class ThreadTest1 extends Thread { @Override public void run() { // 线程代码区 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { ThreadTest1 threadTest1 = new ThreadTest1(); threadTest1.setName("线程1"); threadTest1.start(); System.out.println(Thread.currentThread().toString()); }} 运行结果: 12Thread[main,5,main]线程1:我是通过继承Thread类实现的 🎉 实现Runnable接口 12345678910111213141516171819/** * @author: Xuxu * @date: 2020-08-10 10:19 **/public class ThreadTest2 { static class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public static void main(String[] args) { Thread thread = new Thread(new MyThread()); thread.setName("线程2:我是通过Runnable接口实现的"); thread.start(); System.out.println(Thread.currentThread().toString()); }} 运行结果: 12Thread[main,5,main]线程2:我是通过Runnable接口实现的 🎉 通过Callable和FutureTask创建线程 12345678910111213141516171819202122232425import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;/** * @author: Xuxu * @date: 2020-08-10 10:28 **/public class ThreadTest3 { static class MyThread<Object> implements Callable<Object> { @Override public Object call() { System.out.println(Thread.currentThread().getName()); return null; } } public static void main(String[] args) { Callable<Object> callable = new MyThread<>(); FutureTask<Object> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.setName("线程3:我是通过Callable和FutureTask创建的"); thread.start(); System.out.println(Thread.currentThread().toString()); }} 运行结果: 12Thread[main,5,main]线程3:我是通过Callable和FutureTask创建的 🎉 通过线程池创建线程 123456789101112131415161718192021222324252627282930import java.util.concurrent.ExecutorService;import static java.util.concurrent.Executors.*;/** * @author: Xuxu * @date: 2020-08-10 10:39 **/public class ThreadTest4 { //线程池数量 private static int POOL_NUM = 10; static class MyThread implements Runnable { @Override public void run() { System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName()); } } public static void main(String[] args) { ExecutorService executorService = newFixedThreadPool(5); for (int i = 0; i < POOL_NUM; i++) { MyThread myThread = new MyThread(); executorService.execute(myThread); } //关闭线程池 executorService.shutdown(); }} 运行结果: 12345678910通过线程池方式创建的线程:pool-1-thread-2通过线程池方式创建的线程:pool-1-thread-4通过线程池方式创建的线程:pool-1-thread-1通过线程池方式创建的线程:pool-1-thread-4通过线程池方式创建的线程:pool-1-thread-2通过线程池方式创建的线程:pool-1-thread-3通过线程池方式创建的线程:pool-1-thread-1通过线程池方式创建的线程:pool-1-thread-5通过线程池方式创建的线程:pool-1-thread-2通过线程池方式创建的线程:pool-1-thread-4 🔥 线程运行状态 对于多线程的开发而言,编写程序的过程中总是按照:定义线程的主体类,然后通过Thread类进行线程的启动,但是并不意味着你调用了start()方法,线程就已经开始启动了,因为整体的线程处理有自己的一套运行状态。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"}]},{"title":"Vue 格式化数字为金额格式","slug":"Vue格式化数字为金额格式","date":"2020-08-06T20:04:10.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/6b9072d4.html","link":"","permalink":"https://blog.hasaik.com/posts/6b9072d4.html","excerpt":"","text":"代码: 1234567891011121314151617181920212223242526272829/** * @description 格式化金额 * @param number:要格式化的数字 * @param decimals:保留几位小数 默认0位 * @param decPoint:小数点符号 默认. * @param thousandsSep:千分位符号 默认为, */export const formatMoney = (number, decimals = 0, decPoint = '.', thousandsSep = ',') => { number = (number + '').replace(/[^0-9+-Ee.]/g, '') let n = !isFinite(+number) ? 0 : +number let prec = !isFinite(+decimals) ? 0 : Math.abs(decimals) let sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep let dec = (typeof decPoint === 'undefined') ? '.' : decPoint let s = '' let toFixedFix = function (n, prec) { let k = Math.pow(10, prec) return '' + Math.ceil(n * k) / k } s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.') let re = /(-?\\d+)(\\d{3})/ while (re.test(s[0])) { s[0] = s[0].replace(re, '$1' + sep + '$2') } if ((s[1] || '').length < prec) { s[1] = s[1] || '' s[1] += new Array(prec - s[1].length + 1).join('0') } return s.join(dec)}","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"Java 中 sleep() 和 wait() 的区别","slug":"Java中sleep()和wait()的区别","date":"2020-08-02T18:54:14.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/47cc5feb.html","link":"","permalink":"https://blog.hasaik.com/posts/47cc5feb.html","excerpt":"","text":"🎉 两者区别 对于 sleep() 方法,我们首先要知道该方法属于 Thread 类中。而 wait() 方法则属于 Object 类中的。 sleep() 方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。 调用 sleep() 方法过程中,线程不会释放对象锁。 调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify() 方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 🎉 举例说明 代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758/** * @author: Xuxu * @date: 2020-08-02 18:43 **/public class TestD { public static void main(String[] args) { new Thread(new Thread1()).start(); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } new Thread(new Thread2()).start(); } public static class Thread1 implements Runnable { @Override public void run() { synchronized (TestD.class) { System.out.println("enter thread1..."); System.out.println("thread1 is waiting..."); try { // 调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池 TestD.class.wait(); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread1 is going on ...."); System.out.println("thread1 is over!!!"); } } } public static class Thread2 implements Runnable { @Override public void run() { synchronized (TestD.class) { System.out.println("enter thread2...."); System.out.println("thread2 is sleep...."); // 只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 TestD.class.notify(); //区别 //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify()方法,则线程永远处于挂起状态。 try { //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程, //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 //在调用sleep()方法的过程中,线程不会释放对象锁。 Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread2 is going on...."); System.out.println("thread2 is over!!!"); } } }} 运行结果: 12345678enter thread1...thread1 is waiting...enter thread2....thread2 is sleep....thread2 is going on....thread2 is over!!!thread1 is going on ....thread1 is over!!! 如果注释掉下面这行代码: 1TestD.class.notify(); 运行结果变为: 123456enter thread1...thread1 is waiting...enter thread2....thread2 is sleep....thread2 is going on....thread2 is over!!! 且程序一直处于挂起状态。 经过上面这段代码演示我相信大家会更容易的理解 sleep() 和 wait() 的区别。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"}]},{"title":"Vue Element-UI 采用 http-request 方式自定义文件上传","slug":"Vue-Element-UI采用http-request方式自定义文件上传","date":"2020-07-29T08:42:50.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/461108bb.html","link":"","permalink":"https://blog.hasaik.com/posts/461108bb.html","excerpt":"","text":"Vue 的文件上传组件 upload,拥有支持多种格式文件上传,单文件多文件等都支持,许多项目现在都少不了文件上传功能,但是 vue 的 upload 组件如果直接引用,肯定也有一些不方便之处,有的时候需要传参数,需要手动触发上传方法,而不是选择了文件就上传,所以结合我项目实例,写一 vue 自定义文件上传的实现,包括前端和后台的处理以及参数的接收。 🌞 Vue 界面示例 我这里是富文本中的图片上传,以下教程也同样适用于其他方面,可根据自己需求修改。 🌞 Vue 代码 以下代码可根据自己业务需求进行合理的修改使用。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130<template> <div class="upload-container"> <el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true"> 上传 </el-button> <el-dialog :visible.sync="dialogVisible"> <el-upload :multiple="true" :file-list="fileList" :show-file-list="true" :on-remove="handleRemove" :before-upload="beforeUpload" :http-request="uploadImage" action="#" class="editor-slide-upload" list-type="picture-card" > <el-button size="small" type="primary"> 点击上传 </el-button> </el-upload> <el-button @click="dialogVisible = false"> 取消 </el-button> <el-button type="primary" @click="handleSubmit"> 确定 </el-button> </el-dialog> </div></template><script>import { uploadALocalPicture } from '../../../api/upload'export default { name: 'EditorSlideUpload', props: { color: { type: String, default: '#1890ff' } }, data() { return { dialogVisible: false, listObj: {}, fileList: [] } }, methods: { // 校验所有文件是否上传成功 checkAllSuccess() { return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess) }, // 确定提交事件 handleSubmit() { // 我这里是将数组递交给父级组件,根据自己的需求进行合理处理 const arr = Object.keys(this.listObj).map(v => this.listObj[v]) // 校验所有文件是否上传成功 if (!this.checkAllSuccess()) { this.$message('请等待所有图像成功上传。如果出现网络问题,请刷新页面,然后重新上传!') return } // 集合递交给父级组件方法 this.$emit('successCBK', arr) this.listObj = {} this.fileList = [] this.dialogVisible = false }, // 移除文件 handleRemove(file) { const uid = file.uid const objKeyArr = Object.keys(this.listObj) for (let i = 0, len = objKeyArr.length; i < len; i++) { if (this.listObj[objKeyArr[i]].uid === uid) { delete this.listObj[objKeyArr[i]] return } } }, // 上传文件前调用的方法,主要为了校验文件 beforeUpload(file) { if (!(file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/jpeg' || file.type === 'image/gif')) { this.$notify.warning({ title: '警告', message: '上传应用Logo图片只能是 JPG/PNG/JPEG/GIF 格式!' }) return false } }, // 上传文件 uploadImage(param) { const _self = this const fileName = param.file.uid this.listObj[fileName] = {} const formData = new FormData() formData.append('picFile', param.file) // 这里调用的axios封装的请求方法(根据自己项目进行调用) uploadALocalPicture(formData).then(res => { // 上传成功的图片会显示绿色的对勾 param.onSuccess() console.log('上传图片成功') _self.listObj[fileName] = { hasSuccess: false, uid: param.file.uid } this.handleSuccess(res, param.file) }) }, // 上传成功时调用的方法 handleSuccess(response, file) { const uid = file.uid const objKeyArr = Object.keys(this.listObj) for (let i = 0, len = objKeyArr.length; i < len; i++) { if (this.listObj[objKeyArr[i]].uid === uid) { this.listObj[objKeyArr[i]].url = response.data this.listObj[objKeyArr[i]].hasSuccess = true return } } } }}</script><style lang="scss" scoped> .editor-slide-upload { margin-bottom: 20px; /deep/ .el-upload--picture-card { width: 100%; } }</style> 🌞 Java 代码 以下代码各位看个思路就行,具体代码实现根据自己情况而定。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798package com.scaffolding.demo.sys.controller;import com.scaffolding.demo.annotation.CheckToken;import com.scaffolding.demo.annotation.Log;import com.scaffolding.demo.annotation.PassToken;import com.scaffolding.demo.common.ErrorCode;import com.scaffolding.demo.common.OperationLogConstant;import com.scaffolding.demo.common.Result;import com.scaffolding.demo.common.ResultGenerator;import com.scaffolding.demo.config.EnvConfig;import com.scaffolding.demo.exception.BusinessException;import com.scaffolding.demo.utils.UuidUtil;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponses;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;import java.io.File;import java.io.IOException;import java.util.Objects;/** * @author: Xuxu * @date: 2020-01-16 15:41 **/@Slf4j@RestController@RequestMapping("/sys/upload")@Api(value = "上传文件", tags = "操作上传文件相关API")@ApiResponses({@ApiResponse(code = 400, message = "请求参数没填好"), @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")})public class UploadFileController { // 获取配置文件中的变量 @Autowired private EnvConfig envConfig; /** * 上传图片 * * @return */ @CheckToken @PostMapping("/image") @ApiOperation(value = "上传图片") @Log(operationModule = "上传图片", operationType = OperationLogConstant.UPLOAD, operationDesc = "上传图片") public Result<String> uploadImage(@RequestParam("picFile") MultipartFile picFile) { log.info("开始上传图片"); try { String showPath = uploadFile(picFile); return ResultGenerator.genSuccessResult(showPath); } catch (Exception e) { log.error("上传图片异常"); e.printStackTrace(); throw new BusinessException(ErrorCode.UPLOAD_IMAGE_ERROR); } } /** * 上传文件 * * @param file * @return * @throws IOException */ private String uploadFile(MultipartFile file) throws IOException { //获取原始文件名称(包含格式) String filename = file.getOriginalFilename(); //获取文件类型,以最后一个`.`为标识 String type = filename.substring(Objects.requireNonNull(filename).lastIndexOf(".") + 1); //文件名 String name = filename.substring(0, Objects.requireNonNull(filename).lastIndexOf(".")); //当前时间戳 long timeMillis = System.currentTimeMillis(); //获取文件在服务器的储存位置 File filePath = new File(envConfig.getUploadPath()); // 是否是文件夹 if (!filePath.exists() && !filePath.isDirectory()) { System.out.println("目录不存在,创建目录:" + filePath); filePath.mkdir(); } //在指定路径下创建一个文件 File newFile = new File(envConfig.getUploadPath(), name + timeMillis + "." + type); // 上传文件 file.transferTo(newFile); return envConfig.getShowPath() + name + timeMillis + "." + type; }}","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"},{"name":"文件上传","slug":"文件上传","permalink":"https://blog.hasaik.com/tags/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"}]},{"title":"Java获取两个日期之间包含的年、月、日","slug":"Java获取两个日期之间包含的年、月、日","date":"2020-07-09T19:39:16.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/f4a01af3.html","link":"","permalink":"https://blog.hasaik.com/posts/f4a01af3.html","excerpt":"","text":"不多废话,直接上代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112package com.scaffolding.demo.utils;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Calendar;import java.util.Date;import java.util.List;/** * @author: Xuxu * @date: 2020-06-30 19:36 **/public class DateUtil { public static void main(String[] args) throws ParseException { // TODO Auto-generated method stub String beginTime = "2019-02-01"; String endTime = "2019-02-04"; // 测试天 List<String> daysStr = findDaysStr(beginTime, endTime); System.out.println("所有天:" + daysStr); // 测试月 List<String> monthsStr = findMonthsStr(beginTime, endTime); System.out.println("所有月:" + monthsStr); // 测试年 List<String> yearsStr = findYearsStr(beginTime, endTime); System.out.println("所有年:" + yearsStr); } /** * 计算两个日期之间包含的所有天 * * @param beginTime * @param endTime * @return */ public static List<String> findDaysStr(String beginTime, String endTime) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date dBegin = null; Date dEnd = null; try { dBegin = sdf.parse(beginTime); dEnd = sdf.parse(endTime); } catch (ParseException e) { e.printStackTrace(); } List<String> daysStrList = new ArrayList<String>(); daysStrList.add(sdf.format(dBegin)); Calendar calBegin = Calendar.getInstance(); calBegin.setTime(dBegin); Calendar calEnd = Calendar.getInstance(); calEnd.setTime(dEnd); while (dEnd.after(calBegin.getTime())) { calBegin.add(Calendar.DAY_OF_MONTH, 1); String dayStr = sdf.format(calBegin.getTime()); daysStrList.add(dayStr); } return daysStrList; } /** * 计算两个日期之间包含的所有月份 * * @param beginTime * @param endTime * @return * @throws ParseException */ public static List<String> findMonthsStr(String beginTime, String endTime) throws ParseException { List<String> monthsStrList = new ArrayList<>(); //格式化为年月 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM"); Calendar min = Calendar.getInstance(); Calendar max = Calendar.getInstance(); min.setTime(sdf.parse(beginTime)); min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1); max.setTime(sdf.parse(endTime)); max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2); while (min.before(max)) { monthsStrList.add(sdf.format(min.getTime())); min.add(Calendar.MONTH, 1); } return monthsStrList; } /** * 计算两个日期之间包含的所有年份 * * @param beginTime * @param endTime * @return */ public static List<String> findYearsStr(String beginTime, String endTime) { List<String> yearsStrList = new ArrayList<>(); beginTime = beginTime.substring(0, 4); endTime = endTime.substring(0, 4); if (beginTime.equals(endTime)) { yearsStrList.add(beginTime); } else { yearsStrList.add(beginTime); for (int i = 1; i <= Integer.parseInt(endTime) - Integer.parseInt(beginTime); i++) { yearsStrList.add(String.valueOf(Integer.parseInt(beginTime) + i)); } } return yearsStrList; }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"工具类","slug":"Java/工具类","permalink":"https://blog.hasaik.com/categories/Java/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"}]},{"title":"Vue 使用 XMLHttpRequest 导出 excel","slug":"Vue使用XMLHttpRequest导出excel","date":"2020-07-06T16:41:32.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/34469844.html","link":"","permalink":"https://blog.hasaik.com/posts/34469844.html","excerpt":"","text":"相关代码: 123456789101112131415161718192021222324const formData = new FormData()const xhr = new XMLHttpRequest()xhr.open('post', 'http://localhost:8080/api') // url填写后台的接口地址,如果是post,在formData append参数即可xhr.setRequestHeader('X-Token', getToken())xhr.responseType = 'blob'xhr.onload = function(e) {if (this.status === 200) { const blob = this.response const filename = '合同.xlsx' // 这里的名字,可以按后端给的接口固定表单设置一下名字,如(费用单.xlsx,合同.doc等等) console.log(this.response) if (window.navigator.msSaveOrOpenBlob) { navigator.msSaveBlob(blob, filename) } else { const a = document.createElement('a') const url = URL.createObjectURL(blob) a.href = url a.download = filename document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) }}}xhr.send(formData)","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"Vue v-for list数据循环 每3或者(n)个一组","slug":"Vue v-for list数据循环 每3或者(n)个一组","date":"2020-07-06T15:42:46.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/4fc8b649.html","link":"","permalink":"https://blog.hasaik.com/posts/4fc8b649.html","excerpt":"","text":"template 代码: 12345678910111213<el-carousel-item v-for="(item,index) in listTemp" :key="index"> <div style="margin-left: 17%;margin-right: 10%;margin-top: 20px;"> <div v-for="o in item" :key="o" style="display:inline;padding: 0.6rem"> <el-card :body-style="{ padding: '0px' }" style="width: 20%;max-height: 200px" @click.native="showDrawer(o)"> <img :src="o.imgPath" class="image"> <div style="padding: 10px;font-size:14px;display: flex;justify-content: space-between"> <span>{{ o.eventdate }}</span> <span>{{ o.position }}</span> </div> </el-card> </div> </div></el-carousel-item> computed 代码: 12345678910111213141516computed: { listTemp() { let index = 0 const count = 4 const arrTemp = [] const experts = this.alarmImgList for (let i = 0; i < this.alarmImgList.length; i++) { index = parseInt(i / count) if (arrTemp.length <= index) { arrTemp.push([]) } arrTemp[index].push(experts[i]) } return arrTemp }}","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"Vue获取指定日期的那一周的开始、结束日期(从周日~周六)","slug":"Vue获取指定日期的那一周的开始、结束日期(从周日-周六)","date":"2020-07-06T15:26:10.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/faf888d5.html","link":"","permalink":"https://blog.hasaik.com/posts/faf888d5.html","excerpt":"","text":"需求:给定一个日期,如: 2020-07-06,获取该日期所在的这一周的开始时间、结束时间(按周日~周六算) 代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243// 获取指定日期的那一周的开始、结束日期export function getWeekStartAndEnd(val) { let now if (val) { now = new Date(val) } else { now = new Date() } const nowDayOfWeek = now.getDay() // 本周的第几天 const nowDay = now.getDate() // 当前日 const nowMonth = now.getMonth() // 当前月 const nowYear = now.getFullYear() // 当前年 const weekStart = getWeekStartDate(nowYear, nowMonth, nowDay, nowDayOfWeek) const weekEnd = getWeekEndDate(nowYear, nowMonth, nowDay, nowDayOfWeek) console.log(weekStart + ',' + weekEnd) return weekStart + ',' + weekEnd}// 获得某一周的开始日期export function getWeekStartDate(nowYear, nowMonth, nowDay, nowDayOfWeek) { const weekStartDate = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek) return formatDate(weekStartDate)}// 获得某一周的结束日期export function getWeekEndDate(nowYear, nowMonth, nowDay, nowDayOfWeek) { const weekEndDate = new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek)) return formatDate(weekEndDate)}// 日期格式化export function formatDate(date) { var myYear = date.getFullYear() var myMonth = date.getMonth() + 1 var myWeekday = date.getDate() if (myMonth < 10) { myMonth = '0' + myMonth } if (myWeekday < 10) { myWeekday = '0' + myWeekday } return myYear + '-' + myMonth + '-' + myWeekday}","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"Vue兼容ie下载网络图片","slug":"Vue兼容ie下载网络图片","date":"2020-07-06T10:48:36.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/ef31a0a3.html","link":"","permalink":"https://blog.hasaik.com/posts/ef31a0a3.html","excerpt":"","text":"最近在做 vue 项目中需要将图片下载到本地,而且要兼容 ie ,这就让人很头大,终于功夫不负有心人,解决了此问题,在此记录一下,希望可以帮助到其他人 ✌️。 以下为 js 代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374<script> downloadByBlob(imageUrl, name) { const image = new Image() // 解决跨域问题 image.setAttribute('crossOrigin', 'anonymous') image.src = imageUrl image.onload = () => { const canvas = document.createElement('canvas') canvas.width = image.width canvas.height = image.height const context = canvas.getContext('2d') context.drawImage(image, 0, 0, image.width, image.height) const quality = 0.8 // 这里的dataUrl就是base64类型 const dataUrl = canvas.toDataURL('image/jpeg', quality)// 使用toDataUrl将图片转换成jpeg的格式,不要把图片压缩成png,因为压缩成png后base64的字符串可能比不转换前的长! const arr = dataUrl.split(',') const mime = arr[0].match(/:(.*?);/)[1] const bStr = atob(arr[1]) let n = bStr.length const u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bStr.charCodeAt(n) } const blob_ = new Blob([u8arr], { type: mime }) if (this.myBrowser() === 'IE') { const url = { name: name, src: blob_ } // filename文件名包括扩展名,下载路径为浏览器默认路径 navigator.msSaveBlob(url.src, url.name) } else { const save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a') save_link.href = dataUrl save_link.download = name const event = document.createEvent('MouseEvents') event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null) save_link.dispatchEvent(event) } } }, myBrowser() { const userAgent = window.navigator.userAgent // 取得浏览器的userAgent字符串 const isOpera = userAgent.indexOf('Opera') > -1 // 判断是否Opera浏览器 const isIE = userAgent.indexOf('NET') > -1 && userAgent.indexOf('rv') > -1 // 判断是否IE浏览器 const isEdge = userAgent.indexOf('Edge') > -1 // 判断是否IE的Edge浏览器 const isFF = userAgent.indexOf('Firefox') > -1 // 判断是否Firefox浏览器 const isSafari = userAgent.indexOf('Safari') > -1 && userAgent.indexOf('Chrome') === -1 // 判断是否Safari浏览器 const isChrome = userAgent.indexOf('Chrome') > -1 && userAgent.indexOf('Safari') > -1 // 判断Chrome浏览器 if (isIE) { return 'IE' } if (isOpera) { return 'Opera' } if (isEdge) { return 'Edge' } if (isFF) { return 'FF' } if (isSafari) { return 'Safari' } if (isChrome) { return 'Chrome' } }</script>","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"事务的四个特性以及事务的隔离级别","slug":"事务的四个特性以及事务的隔离级别","date":"2020-05-14T10:02:08.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/fe5c5e4f.html","link":"","permalink":"https://blog.hasaik.com/posts/fe5c5e4f.html","excerpt":"","text":"🎵 什么是事务 事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。可以简单理解为:就是把多件事情当做一件事情来处理,好比大家同在一条船上,要活一起活,要完一起完 。 🔥 为什么需要事务 事务是为解决 数据安全 操作提出的,事务控制实际上就是控制数据的安全访问。 primary,Example:银行转帐业务,账户A要将自己账户上的1000元转到B账户下面,A账户余额首先要减去1000元,然后B账户要增加1000元。假如在中间网络出现了问题,A账户减去1000元已经结束,B因为网络中断而操作失败,那么整个业务失败,必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操作就需要事务,将A账户资金减少和B账户资金增加放到同一个事务里,要么全部执行成功,要么全部撤销,这样就保证了数据的安全性。 🎉 事物的四个特性(ACID) 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。Example:拿转账来说,假设用户A和用户B两者的钱加起来一共是20000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是20000,这就是事务的一致性。 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。Example:比如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。 🎃 并发事务导致的问题 在许多事务处理同一个数据时,如果没有采取有效的隔离机制,那么并发处理数据时,会带来一些严重的问题。 第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。Example:小明到银行存钱,他的账户里原来的余额为100元,现在打算存入100元。在他存钱的过程中,银行年费扣了5元,余额只剩95元。突然他又想着这100元要用来请女朋友看电影吃饭,不打算存了。在他撤回存钱操作后,余额依然为他存钱之前的100元。所以那5块钱到底扣了谁的? 脏读(针对未提交数据):脏读是指在一个事务处理过程中读取了另一个未提交事务中的数据。Example:小明的银行卡余额中有100元,现在他准备点一份外卖,需要付款15元,但是这个时候,他的女朋友逛街看中了一件衣服95元,她也正在使用小明的银行卡进行付款。于是小明在付款的时候,程序后台只读取到他的余额只有5块钱了,不够付款去点外卖了,所以系统拒绝了他的交易,并告知余额不足。但是小明的女朋友最后因为密码不记得,无法进行交易。小明非常郁闷,明明银行卡中还有100元,怎么会余额不足呢? 幻读也叫虚读(针对增删操作):一个事务执行两次查询,第二次结果集包含了第一次中没有或者已经被删除的数据,造成了两次的结果不一致,只因另一个事务在这两次查询中进行插入或删除数据造成的。幻读是事务非独立执行的时候产生的一种现象。Example:事务T1对一个表中所有行的某个数据项做了从“1”改为“2”的操作,这时事务T2又对这个表中插入了一行该数据项为“1”的数据,并且提交给数据库。而操作事务T1的用户再次查看刚刚修改的数据时,发现还有一行没有被修改,其实是因为这个数据是从事务T2添加的,这就好像产生了幻觉一样,所以称之为幻读。 不可重复读(针对修改操作):一个事务两次读取同一行数据,结果得到不同状态的结果。是因为正好另一个事务更新了该数据,造成结果不同。Example:例如小明到银行进行查询自己的银行卡余额为100万,但是就在这个时候他女朋友从该银行卡取走50万,此时余额变为50万,小明再一次查询余额变成了50万,小明心态炸了(哈哈哈),这对于小明而言两次结果不一致就是不可重复读。 第二类丢失更新:是不可重复读的情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。Example:小明和女朋友一起去逛街。女朋友看中了一支口红,小明大方的掏出了自己的银行卡,告诉女朋友:亲爱的,随便刷,随便买,我坐着等你。然后小明就坐在商城座椅上玩手机,等着女朋友。这个时候,程序员的聊天群里有人推荐了一本书,小明一看,哎呀,真是本好书,还是限量发行呢,我一定更要买到。于是小明赶紧找到购买渠道,进行付款操作。而同时,小明的女朋友也在不亦乐乎的买买买,他们同时进行了一笔交易操作,但是这个时候银行系统出了问题,当他们都付款成功后,却发现,银行只扣了小明的买书钱,却没有扣去女朋友此时交易的钱。哈哈哈,小明真是太开心了! 🎧 数据库事务的隔离级别 为了应对上面并发情况下出现的问题,事务的隔离级别就产生了。当事务的隔离级别越高的时候,上面的问题就会越少,但是性能消耗也会越大。所以在实际生产过程中,要根据需求去确定隔离级别。事务隔离级别由低到高分别为 Read uncommitted 、Read committed 、Repeatable read 、Serializable 。 Read uncommitted(读未提交):读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种。 Read committed(读已提交):已提交,即能够读到那些已经提交的数据,能够防止脏读,但是无法解决不可重复读和幻读的问题。 Repeatable read(可重复读):重复读取,即在数据读出来之后加锁,类似“select * from xxx for update”,明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。Repeatable read 的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决。 Serializable(序列化):串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。 🔔 提示 大多数数据库默认的事务隔离级别是 Read committed(读提交),比如 Sql Server、Oracle。Mysql 的默认隔离级别是 Repeatable read(重复读)。 隔离级别的设置只对当前链接有效。对于使用 MySQL 命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于 JDBC 操作数据库来说,一个 Connection 对象相当于一个链接,而对于 Connection 对象设置的隔离级别只对该 Connection 对象有效,与其他链接 Connection 对象无关。 设置数据库的隔离级别一定要是在开启事务之前。","categories":[{"name":"MySql","slug":"MySql","permalink":"https://blog.hasaik.com/categories/MySql/"},{"name":"事务","slug":"MySql/事务","permalink":"https://blog.hasaik.com/categories/MySql/%E4%BA%8B%E5%8A%A1/"}],"tags":[{"name":"事务","slug":"事务","permalink":"https://blog.hasaik.com/tags/%E4%BA%8B%E5%8A%A1/"}]},{"title":"Vue 获取 url 上的参数","slug":"about-vue-getQueryObject","date":"2020-05-08T08:51:58.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/41567ba6.html","link":"","permalink":"https://blog.hasaik.com/posts/41567ba6.html","excerpt":"","text":"经常我们在项目中需要获取 url 上参数进行使用,所以今天为大家奉上工具类 💜 代码: 12345678910111213141516171819/** * 获取url上的参数 * @param {string} url * @returns {Object} */export function getQueryObject(url) { url = url == null ? window.location.href : url const search = url.substring(url.lastIndexOf('?') + 1) const obj = {} const reg = /([^?&=]+)=([^?&=]*)/g search.replace(reg, (rs, $1, $2) => { const name = decodeURIComponent($1) let val = decodeURIComponent($2) val = String(val) obj[name] = val return rs }) return obj} 需要的小伙伴自取吧 ✌️","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"SpringBoot 使用 Allatori 进行代码混淆","slug":"about-springboot-allatori","date":"2020-04-29T12:55:35.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/7fabafee.html","link":"","permalink":"https://blog.hasaik.com/posts/7fabafee.html","excerpt":"","text":"🌞 Allatori介绍 Allatori是一个 Java 混淆器,它属于第二代混淆器,因此它能够全方位地保护你的知识产权。 Allatori具有以下几种保护方式:命名混淆,流混淆,调试信息混淆,字符串混淆,以及水印技术。对于教育和非商业项目来说这个混淆器是免费的。支持war和jar文件格式,并且允许对需要混淆代码的应用程序添加有效日期。 有项目需要对代码进行保护,比较初级的方案就是对代码进行混淆,打包之后的文件进行反编译后,就可以看到效果。此外,使用Allatori打的包体积也会小一点。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}GitHub地址https://github.com/Lovnx/confusion 🌞 使用原因 在很多企业中,代码管理是非常重要的,每个企业都有自己的私有机制,生产出一好的产品,又不想让别人知道自己的技术是如何实现的,就会让搞编程的人,或者是安全师对代码进行保护,这里就是运用Allatori技术对class文件中的代码进行混淆,继而使得其他人即便获取到程序,反编译程序也是徒劳无功、无功而返,这就保证了自己代码的专利性。 🌞 配置步骤 🎉 项目目录结构 🎉 准备资源 下载 allatori.jar 和 allatori-annotations.jar ,下载地址:http://www.allatori.com/,将下载好的文件放到上面项目目录的 lib 下。 🎉 创建allatori.xml文件 在创建的文件中放入一下内容: 12345678910111213141516171819202122<config> <input> <!-- in中是待混淆的war包,out是混淆后的war包 --> <jar in="AIReminder-0.0.1-SNAPSHOT.war" out="AIReminder-0.0.1-SNAPSHOT-obfuscated.war"/> </input> <ignore-classes> <!-- 配置了启动相关类不被混淆,保证springboot可以正常启动 --> <!-- 以下请根据自己项目的实际需求进行配置 --> <class template="class com.scaffolding.demo.sys.controller.UploadFileController" /> <class template="class *model*" /> <class template="class *service*" /> <class template="class *utils*" /> <class template="class *config*" /> </ignore-classes> <keep-names> <class access="protected+"> <field access="protected+"/> <method access="protected+"/> </class> </keep-names> <property name="log-file" value="log.xml"/></config> 还有很多配置可以参考官方文档:官网文档 🎉 修改pom.xml文件 查看pom.xml代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667<build> <plugins> <plugin> <!-- springboot打包插件 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <!-- resouces拷贝文件插件 --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> <executions> <!-- 执行这个插件的时候执行申明的所有phase --> <execution> <id>copy-and-filter-allatori-config</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <!-- 这个地方需要注意拷贝的文件位置需要是target目录,target目录是最终打jar包存放的位置 --> <outputDirectory>${basedir}/target</outputDirectory> <resources> <resource> <!-- 这个地方的文件目录需要注意,网上很多目录都是${basedir}/allatori这个,所以才会需要手动拷贝allatori.xml文件到target目录下,有了这个mvn会自动帮我们拷贝了 --> <directory>src/main/resources</directory> <includes> <!-- 配置文件文件名 --> <include>allatori.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <!-- 代码混淆打包插件 --> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <id>run-allatori</id> <phase>package</phase> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <executable>java</executable> <arguments> <argument>-Xms128m</argument> <argument>-Xmx512m</argument> <argument>-jar</argument> <!-- 指定引用的allatori的jar包位置,这里把jar包放在了根目录下的lib目录里 --> <argument>${basedir}/lib/allatori.jar</argument> <!-- 指定代码混淆时的配置文件,因为是混淆指定的是jar包,jar包位置在target下,所以我们的allatori.xml也需要拷贝到该目录下 --> <argument>${basedir}/target/allatori.xml</argument> </arguments> </configuration> </plugin> </plugins></build> 🎉 打包 在控制台中执行 mvn clean 再执行 mvn package -DskipTests 执行成功以后,在项目的target目录下会生成你想要的war包 打包好的war包放到 tomcat 下就可以直接部署啦! 如果在部署过程中报错,看一下错误信息,可能是某个类或者方法不能做混淆,就需要在 allatori.xml 中配置相关类不被混淆。","categories":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/categories/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/tags/SpringBoot/"},{"name":"Allatori","slug":"Allatori","permalink":"https://blog.hasaik.com/tags/Allatori/"}]},{"title":"Hexo+Next7.8.0 引入 Folding 容器","slug":"about-hexo-folding","date":"2020-04-27T13:46:25.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/d0bc4f5d.html","link":"","permalink":"https://blog.hasaik.com/posts/d0bc4f5d.html","excerpt":"","text":"偶然间看到了html5的 <summary> 标签,可以将内容展开折叠,感觉很棒,就将其封装到 Next 主题中使用。 本文介绍的文件路径及方法都是基于 Next7.8.0 版本实现的,小伙伴采用且考虑兼容性。 🌞 使用方法 语法格式参数列表示例写法示例效果12345{% folding 参数(可选), 标题 %}![](https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/resume/resumeBg.jpg){% endfolding %}参数位置可以填写颜色和状态,多个参数用空格隔开。 颜色 1blue, cyan, green, yellow, red 状态 状态填写 open 代表默认打开。123456789101112131415161718192021222324252627282930313233343536{% folding 查看图片测试 %}![](https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/resume/resumeBg.jpg){% endfolding %}{% folding cyan open, 查看默认打开的折叠框 %}这是一个默认打开的折叠框。{% endfolding %}{% folding green, 查看代码测试 %}{% endfolding %}{% folding yellow, 查看列表测试 %}- haha- hehe{% endfolding %}{% folding red, 查看嵌套测试 %}{% folding blue, 查看嵌套测试2 %}{% folding 查看嵌套测试3 %}hahaha{% endfolding %}{% endfolding %}{% endfolding %} 查看图片测试 查看默认打开的折叠框 这是一个默认打开的折叠框。 查看代码测试 1查看代码测试 查看列表测试 hahahehe 查看嵌套测试 查看嵌套测试2 查看嵌套测试3 吼吼 ✌️ 🌞 配置 🎉 引入js 在 \\next\\scripts\\tags 目录下新建文件 folding.js,并添加以下内容: 1234567891011121314151617181920212223242526272829303132333435363738394041424344'use strict';/** * Usage: * {% folding [args], title %} * content * {% endfolding %} * * args: * - color: blue, cyan, green, yellow, red * - status: open # means open by default * * example: * {% folding cyan open, view the default folding box %} * This is a folding box that opens by default * {% endfolding %} */function postFolding(args, content) { args = args.join(' ').split(','); let style = '' let title = '' if (args.length > 1) { style = args[0].trim() title = args[1].trim() } else if (args.length > 0) { title = args[0].trim() } if (style != undefined) { return `<details ${style}><summary> ${hexo.render.renderSync({text: title, engine: 'markdown'}).split('\\n').join('')} </summary> <div class='content'> ${hexo.render.renderSync({text: content, engine: 'markdown'}).split('\\n').join('')} </div> </details>`; } else { return `<details><summary> ${hexo.render.renderSync({text: title, engine: 'markdown'}).split('\\n').join('')} </summary> <div class='content'> ${hexo.render.renderSync({text: content, engine: 'markdown'}).split('\\n').join('')} </div> </details>`; }}hexo.extend.tag.register('folding', postFolding, {ends: true}); 🎉 引入css 在 \\next\\source\\css\\_common\\scaffolding\\tags 目录下新建文件 folding.styl,并添加以下内容: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166// gap$fd-gap = 16px // base gap$fd-gap-paragraph = 1rem // block spacing$fd-gap-row = .5rem // line spacing$fd-gap-card = $fd-gap// border radius$fd-border-codeblock = 4px// common$fd-color-card = white$fd-color-block = #f6f6f6// font size$fd-fontsize-meta = .875rem // 14px$fd-fontsize-list = .9375rem // 15px//color$fd-color-p = #555$fd-color-md-blue = #e8f4fd$fd-color-md-blue1 = rgba(33,150,243,0.3)$fd-color-mac-cyan = #e8fafe$fd-color-mac-cyan1 = rgba(27,205,252,0.3)$fd-color-mac-green = #ebf9ed$fd-color-mac-green1 =rgba(61,197,80,.3)$fd-color-mac-yellow = #fff8e9$fd-color-mac-yellow1 = rgba(255,189,43,0.3)$fd-color-mac-red = #feefee$fd-color-mac-red1 = rgba(254,95,88,0.3)// transition time$fd-time = 0.28sdetails display: block padding: $fd-gap margin: $fd-gap-row 0 border-radius: $fd-border-codeblock background: $fd-color-card font-size: $fd-fontsize-list transition: all $fd-time ease -moz-transition: all $fd-time ease -webkit-transition: all $fd-time ease -o-transition: all $fd-time ease summary cursor: pointer; padding: $fd-gap outline:none; margin: 0 - $fd-gap border-radius: $fd-border-codeblock color: alpha($fd-color-p, .7) font-size: $fd-fontsize-meta font-weight: bold position: relative line-height: normal >p, h1, h2, h3, h4, h5, h6 display: inline !important border-bottom: none !important cursor: pointer; &:hover color: $fd-color-p &:after position: absolute content: '+' text-align: center top: 50% transform: translateY(-50%) right: $fd-gap border: 1px solid $fd-color-block > summary background: $fd-color-block &[blue] border-color: $fd-color-md-blue > summary background:$fd-color-md-blue &[cyan] border-color:$fd-color-mac-cyan > summary background:$fd-color-mac-cyan &[green] border-color:$fd-color-mac-green > summary background:$fd-color-mac-green &[yellow] border-color:$fd-color-mac-yellow > summary background:$fd-color-mac-yellow &[red] border-color:$fd-color-mac-red > summary background:$fd-color-mac-reddetails[open] border-color: alpha($fd-color-p, .2) > summary border-bottom: 1px solid alpha($fd-color-p, .2) border-bottom-left-radius: 0 border-bottom-right-radius: 0 &[blue] border-color: alpha($fd-color-md-blue1, .3) > summary border-bottom-color: alpha($fd-color-md-blue1, .3) &[cyan] border-color: alpha($fd-color-mac-cyan1, .3) > summary border-bottom-color: alpha($fd-color-mac-cyan1, .3) &[green] border-color: alpha($fd-color-mac-green1, .3) > summary border-bottom-color: alpha($fd-color-mac-green1, .3) &[yellow] border-color: alpha($fd-color-mac-yellow1, .3) > summary border-bottom-color: alpha($fd-color-mac-yellow1, .3) &[red] border-color: alpha($fd-color-mac-red1, .3) > summary border-bottom-color: alpha($fd-color-mac-red1, .3) > summary color: $fd-color-p margin-bottom: 0 &:hover &:after content: '-' > div.content padding: $fd-gap margin: 0 - $fd-gap margin-top: 0 p > a:hover text-decoration: underline > p, .tabs, ul, ol, .highlight, .note, .fancybox, details &:first-child margin-top: 0 &:last-child margin-bottom: 0 最后在目录 \\next\\source\\css\\_common\\scaffolding\\tags 下有一个文件 tags.styl,添加以下内容: 1@import 'folding'; 配上完以上内容,素质三连 hexo clean && hexo g && hexo s 就可以在 Markdown 中使用啦!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Folding容器","slug":"Folding容器","permalink":"https://blog.hasaik.com/tags/Folding%E5%AE%B9%E5%99%A8/"}]},{"title":"CentOS搭建Gitlab的详细教程","slug":"CentOS搭建Gitlab的详细教程","date":"2020-04-16T14:05:16.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/a460b91e.html","link":"","permalink":"https://blog.hasaik.com/posts/a460b91e.html","excerpt":"","text":"记录一次CentOS搭建gitlab服务器的经历。 🌞 前期准备 服务器:CentOS7 安装文件:gitlab-ce-12.6.3-ce.0.el7.x86_64.rpm 🌞 安装gitlab 介绍一下两种安装方式 yum安装、rmp安装(个人喜欢第二种方式,有时候yum下载的有点慢)。 🎉 yum安装 这里直接参考官网安装教程 打开linux系统终端,首先安装gitlab必须的ssh,以及在系统防火墙中打开HTTP、HTTPS和SSH访问。 123456sudo yum install -y curl policycoreutils-python openssh-serversudo systemctl enable sshdsudo systemctl start sshdsudo firewall-cmd --permanent --add-service=httpsudo firewall-cmd --permanent --add-service=httpssudo systemctl reload firewalld 然后是安装发送邮件功能的postfix 123sudo yum install postfixsudo systemctl enable postfixsudo systemctl start postfix 添加gitlab的包仓库(ee改成ce) 1curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash 安装gitlab(EXTERNAL_URL指的是你的gitlab访问地址,这里改为http://+你的linux系统ip) 1sudo EXTERNAL_URL="https://gitlab.example.com" yum install -y gitlab-ce 🎉 rpm安装 使用官网的安装方式下载很慢(亲测网上的阿里站点,清华站点也不是很快),这里教大家直接下载rmp安装包手动安装。 首先去官网安装包仓库下载我们所需的安装包版本 下载完成之后将文件拷贝至你的linux服务器,同样需要配置ssh、防火墙、postfix, 1234567891011//安装gitlab必须的ssh,以及在系统防火墙中打开HTTP、HTTPS和SSH访问。sudo yum install -y curl policycoreutils-python openssh-serversudo systemctl enable sshdsudo systemctl start sshdsudo firewall-cmd --permanent --add-service=httpsudo firewall-cmd --permanent --add-service=httpssudo systemctl reload firewalld//安装发送邮件功能的postfixsudo yum install postfixsudo systemctl enable postfixsudo systemctl start postfix 然后cd进入你的安装包路径进行安装 12//安装 example.rpm 包并在安装过程中显示正在安装的文件信息及安装进度rpm -ivh example.rpm 出现下图即为安装成功 这种方式需要我们手动进入配置文件中修改访问地址 123sudo vim /etc/gitlab/gitlab.rb//修改文件中external_url 'http://你linux的ip' 并且我们还需要修改默认的gitlab clone地址,要不每次都得自己修改 修改文件配置 1sudo vim /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml 将图片上标红处的Host替换成你的域名或ip 🌞 访问 重置并启动GitLab,执行以下命令 123gitlab-ctl reconfiguregitlab-ctl restart 提示 "ok: run:"表示启动成功 然后浏览器上输入你的访问地址(第一次访问会让你输入新密码,用户名默认为root) 🌞 安装过程中遇到的问题 在浏览器中访问GitLab出现502错误: 原因:内存不足。 解决办法:检查系统的虚拟内存是否随机启动了,如果系统无虚拟内存,则增加虚拟内存,再重新启动系统。 8080端口冲突: 原因:由于unicorn默认使用的是 8080 端口。 解决办法:打开 /etc/gitlab/gitlab.rb ,打开 # unicorn['port'] = 8080 的注释,将 8080 修改为 9999 ,保存后运行 sudo gitlab-ctl reconfigure 即可。 以上为CentOS搭建Gitlab的全部教程,如有不对欢迎指出。","categories":[{"name":"Gitlab","slug":"Gitlab","permalink":"https://blog.hasaik.com/categories/Gitlab/"}],"tags":[{"name":"CentOS","slug":"CentOS","permalink":"https://blog.hasaik.com/tags/CentOS/"},{"name":"Gitlab","slug":"Gitlab","permalink":"https://blog.hasaik.com/tags/Gitlab/"}]},{"title":"Spring Boot 通用解决 LocalDateTime 转为字符串后中间含“T”","slug":"about-springboot-LocalDateTile-T","date":"2020-04-15T10:37:29.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/45c03634.html","link":"","permalink":"https://blog.hasaik.com/posts/45c03634.html","excerpt":"","text":"通用解决LocalDateTime转为字符串后中间含“T”的问题 ✌️ 本文参考自:https://juejin.im/post/5ceb4f156fb9a07f06554b63 12345678910111213141516171819202122232425262728package com.scaffolding.base.config;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;/** * 解决 LocalDateTime 中间含 T 的问题 * * @author xuxu */@Configurationpublic class LocalDateTimeSerializerConfig { @Bean public LocalDateTimeSerializer localDateTimeDeserializer() { return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer()); }}","categories":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/categories/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/tags/SpringBoot/"},{"name":"LocalDateTime","slug":"LocalDateTime","permalink":"https://blog.hasaik.com/tags/LocalDateTime/"}]},{"title":"Hexo中使用emoji表情","slug":"about-hexo-emoji","date":"2020-04-08T11:44:02.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/9b280ea3.html","link":"","permalink":"https://blog.hasaik.com/posts/9b280ea3.html","excerpt":"","text":"Hexo 开启欢乐的 emoji 之旅 💛 Hexo 默认的 markdown 渲染引擎不支持将 Github emoji 渲染到静态的 html 页面中,我们换一个支持 emoji 的引擎,再增加一个 emoji 插件即可. 🌞 安装 命令行如下: 123npm un hexo-renderer-marked --savenpm i hexo-renderer-markdown-it --savenpm install markdown-it-emoji --save Tips:据说 hexo-renderer-markdown-it 的速度要比 Hexo 原装插件要快,而且功能更多 🌞 配置 完成插件安装后还需要修改 Hexo 站点配置文件 _config.yml(不是主题配置哦) 123456789101112131415161718192021## markdown 渲染引擎配置,默认是hexo-renderer-marked,这个插件渲染速度更快,且有新特性markdown: render: html: true xhtmlOut: false breaks: true linkify: true typographer: true quotes: '“”‘’' plugins: - markdown-it-footnote - markdown-it-sup - markdown-it-sub - markdown-it-abbr - markdown-it-emoji anchors: level: 2 collisionSuffix: 'v' permalink: true permalinkClass: header-anchor permalinkSymbol: ¶ 这里需要注意 render: 下的 html: 配置项,它的作用是控制 Markdown 渲染引擎是否转义文档中出现的 html 标签,默认为 false ,这里要设置为 true,否则回导致 <!--more--> 渲染失败。 123html: true # 不转义 HTML 内容,即允许 HTML ## ORhtml: false # 转义 HTML,< > 尖括号会被转义成 &lt; &gt;等 plugins: 中的最后一项 - markdown-it-emoji 是手动添加的,官方 Github Wiki 中给出的配置不包含这一项,其他配置参照的 Github Wiki 中的默认配置,hexo-renderer-markdown-it 提供的其他新特性还没有一一尝试,暂时只想用它的 emoji 功能。✌️ 🌞 使用方法 输入对应的emoji编码就行了 例如:输入笑脸对应的 emoji 编码 :smile: 就可以得到 😄 🌞 emoji编码合集 People 😐 :neutral_face: 😄 :smile: 😆 :laughing: 😊 :blush: 😃 :smiley: ☺️ :relaxed: 😏 :smirk: 😍 :heart_eyes: 😘 :kissing_heart: 😚 :kissing_closed_eyes: 😳 :flushed: 😌 :relieved: 😆 :satisfied: 😁 :grin: 😉 :wink: 😜 :stuck_out_tongue_winking_eye: 😝 :stuck_out_tongue_closed_eyes: 😀 :grinning: 😗 :kissing: 😙 :kissing_smiling_eyes: 😛 :stuck_out_tongue: 😴 :sleeping: 😟 :worried: 😦 :frowning: 😧 :anguished: 😮 :open_mouth: 😬 :grimacing: 😕 :confused: 😯 :hushed: 😑 :expressionless: 😒 :unamused: 😅 :sweat_smile: 😓 :sweat: 😥 :disappointed_relieved: 😩 :weary: 😔 :pensive: 😞 :disappointed: 😖 :confounded: 😨 :fearful: 😰 :cold_sweat: 😣 :persevere: 😢 :cry: 😭 :sob: 😂 :joy: 😲 :astonished: 😱 :scream: 💭 :thought_balloon: 😫 :tired_face: 😠 :angry: 😡 :rage: 😤 :triumph: 😪 :sleepy: 😋 :yum: 😷 :mask: 😎 :sunglasses: 😵 :dizzy_face: 👿 :imp: 😈 :smiling_imp: 💬 :speech_balloon: 😶 :no_mouth: 😇 :innocent: 👽 :alien: 💛 :yellow_heart: 💙 :blue_heart: 💜 :purple_heart: ❤️ :heart: 💚 :green_heart: 💔 :broken_heart: 💓 :heartbeat: 💗 :heartpulse: 💕 :two_hearts: 💞 :revolving_hearts: 💘 :cupid: 💖 :sparkling_heart: ✨ :sparkles: ⭐️ :star: 🌟 :star2: 💫 :dizzy: 💥 :boom: 💥 :collision: 💢 :anger: ❗️ :exclamation: ❓ :question: ❕ :grey_exclamation: ❔ :grey_question: 💤 :zzz: 💨 :dash: 💦 :sweat_drops: 🎶 :notes: 🎵 :musical_note: 🔥 :fire: 💩 :hankey: 💩 :poop: 💩 :shit: 👍 :+1: 👍 :thumbsup: 👎 :-1: 👎 :thumbsdown: 👌 :ok_hand: 👊 :punch: 👊 :facepunch: ✊ :fist: ✌️ :v: 👋 :wave: ✋ :hand: ✋ :raised_hand: 👐 :open_hands: ☝️ :point_up: 👇 :point_down: 👈 :point_left: 👉 :point_right: 🙌 :raised_hands: 🙏 :pray: 👆 :point_up_2: 👏 :clap: 💪 :muscle: 🤘 :metal: 🖕 :fu: 🚶 :walking: 🏃 :runner: 🏃 :running: 👫 :couple: 👪 :family: 👬 :two_men_holding_hands: 👭 :two_women_holding_hands: 💃 :dancer: 👯 :dancers: 🙆 :ok_woman: 🙅 :no_good: 💁 :information_desk_person: 🙋 :raising_hand: 👰 :bride_with_veil: 🙎 :person_with_pouting_face: 🙍 :person_frowning: 🙇 :bow: :couplekiss: :couplekiss: 💑 :couple_with_heart: 💆 :massage: 💇 :haircut: 💅 :nail_care: 👦 :boy: 👧 :girl: 👩 :woman: 👨 :man: 👶 :baby: 👵 :older_woman: 👴 :older_man: 👱 :person_with_blond_hair: 👲 :man_with_gua_pi_mao: 👳 :man_with_turban: 👷 :construction_worker: 👮 :cop: 👼 :angel: 👸 :princess: 😺 :smiley_cat: 😸 :smile_cat: 😻 :heart_eyes_cat: 😽 :kissing_cat: 😼 :smirk_cat: 🙀 :scream_cat: 😿 :crying_cat_face: 😹 :joy_cat: 😾 :pouting_cat: 👹 :japanese_ogre: 👺 :japanese_goblin: 🙈 :see_no_evil: 🙉 :hear_no_evil: 🙊 :speak_no_evil: 💂 :guardsman: 💀 :skull: 🐾 :feet: 👄 :lips: 💋 :kiss: 💧 :droplet: 👂 :ear: 👀 :eyes: 👃 :nose: 👅 :tongue: 💌 :love_letter: 👤 :bust_in_silhouette: 👥 :busts_in_silhouette: Nature ☀️ :sunny: ☔️ :umbrella: ☁️ :cloud: ❄️ :snowflake: ⛄️ :snowman: ⚡️ :zap: 🌀 :cyclone: 🌁 :foggy: 🌊 :ocean: 🐱 :cat: 🐶 :dog: 🐭 :mouse: 🐹 :hamster: 🐰 :rabbit: 🐺 :wolf: 🐸 :frog: 🐯 :tiger: 🐨 :koala: 🐻 :bear: 🐷 :pig: 🐽 :pig_nose: 🐮 :cow: 🐗 :boar: 🐵 :monkey_face: 🐒 :monkey: 🐴 :horse: 🐎 :racehorse: 🐫 :camel: 🐑 :sheep: 🐘 :elephant: 🐼 :panda_face: 🐍 :snake: 🐦 :bird: 🐤 :baby_chick: 🐥 :hatched_chick: 🐣 :hatching_chick: 🐔 :chicken: 🐧 :penguin: 🐢 :turtle: 🐛 :bug: 🐝 :honeybee: 🐜 :ant: 🐞 :beetle: 🐌 :snail: 🐙 :octopus: 🐠 :tropical_fish: 🐟 :fish: 🐳 :whale: 🐋 :whale2: 🐬 :dolphin: 🐄 :cow2: 🐏 :ram: 🐀 :rat: 🐃 :water_buffalo: 🐅 :tiger2: 🐇 :rabbit2: 🐉 :dragon: 🐐 :goat: 🐓 :rooster: 🐕 :dog2: 🐖 :pig2: 🐁 :mouse2: 🐂 :ox: 🐲 :dragon_face: 🐡 :blowfish: 🐊 :crocodile: 🐪 :dromedary_camel: 🐆 :leopard: 🐈 :cat2: 🐩 :poodle: 🐾 :paw_prints: 💐 :bouquet: 🌸 :cherry_blossom: 🌷 :tulip: 🍀 :four_leaf_clover: 🌹 :rose: 🌻 :sunflower: 🌺 :hibiscus: 🍁 :maple_leaf: 🍃 :leaves: 🍂 :fallen_leaf: 🌿 :herb: 🍄 :mushroom: 🌵 :cactus: 🌴 :palm_tree: 🌲 :evergreen_tree: 🌳 :deciduous_tree: 🌰 :chestnut: 🌱 :seedling: 🌼 :blossom: 🌾 :ear_of_rice: 🐚 :shell: 🌐 :globe_with_meridians: 🌞 :sun_with_face: 🌝 :full_moon_with_face: 🌚 :new_moon_with_face: 🌑 :new_moon: 🌒 :waxing_crescent_moon: 🌓 :first_quarter_moon: 🌔 :waxing_gibbous_moon: 🌕 :full_moon: 🌖 :waning_gibbous_moon: 🌗 :last_quarter_moon: 🌘 :waning_crescent_moon: 🌜 :last_quarter_moon_with_face: 🌛 :first_quarter_moon_with_face: 🌔 :moon: 🌍 :earth_africa: 🌎 :earth_americas: 🌏 :earth_asia: 🌋 :volcano: 🌌 :milky_way: ⛅️ :partly_sunny: Objects 🎍 :bamboo: 💝 :gift_heart: 🎎 :dolls: 🎒 :school_satchel: 🎓 :mortar_board: 🎏 :flags: 🎆 :fireworks: 🎇 :sparkler: 🎐 :wind_chime: 🎑 :rice_scene: 🎃 :jack_o_lantern: 👻 :ghost: 🎅 :santa: 🎄 :christmas_tree: 🎁 :gift: 🔔 :bell: 🔕 :no_bell: 🎋 :tanabata_tree: 🎉 :tada: 🎊 :confetti_ball: 🎈 :balloon: 🔮 :crystal_ball: 💿 :cd: 📀 :dvd: 💾 :floppy_disk: 📷 :camera: 📹 :video_camera: 🎥 :movie_camera: 💻 :computer: 📺 :tv: 📱 :iphone: ☎️ :phone: ☎️ :telephone: 📞 :telephone_receiver: 📟 :pager: 📠 :fax: 💽 :minidisc: 📼 :vhs: 🔉 :sound: 🔈 :speaker: 🔇 :mute: 📢 :loudspeaker: 📣 :mega: ⌛️ :hourglass: ⏳ :hourglass_flowing_sand: ⏰ :alarm_clock: ⌚️ :watch: 📻 :radio: 📡 :satellite: ➿ :loop: 🔍 :mag: 🔎 :mag_right: 🔓 :unlock: 🔒 :lock: 🔏 :lock_with_ink_pen: 🔐 :closed_lock_with_key: 🔑 :key: 💡 :bulb: 🔦 :flashlight: 🔆 :high_brightness: 🔅 :low_brightness: 🔌 :electric_plug: 🔋 :battery: 📲 :calling: ✉️ :email: 📫 :mailbox: 📮 :postbox: 🛀 :bath: 🛁 :bathtub: 🚿 :shower: 🚽 :toilet: 🔧 :wrench: 🔩 :nut_and_bolt: 🔨 :hammer: 💺 :seat: 💰 :moneybag: 💴 :yen: 💵 :dollar: 💷 :pound: 💶 :euro: 💳 :credit_card: 💸 :money_with_wings: 📧 :e-mail: 📥 :inbox_tray: 📤 :outbox_tray: ✉️ :envelope: 📨 :incoming_envelope: 📯 :postal_horn: 📪 :mailbox_closed: 📬 :mailbox_with_mail: 📭 :mailbox_with_no_mail: 🚪 :door: 🚬 :smoking: 💣 :bomb: 🔫 :gun: 🔪 :hocho: 💊 :pill: 💉 :syringe: 📄 :page_facing_up: 📃 :page_with_curl: 📑 :bookmark_tabs: 📊 :bar_chart: 📈 :chart_with_upwards_trend: 📉 :chart_with_downwards_trend: 📜 :scroll: 📋 :clipboard: 📆 :calendar: 📅 :date: 📇 :card_index: 📁 :file_folder: 📂 :open_file_folder: ✂️ :scissors: 📌 :pushpin: 📎 :paperclip: ✒️ :black_nib: ✏️ :pencil2: 📏 :straight_ruler: 📐 :triangular_ruler: 📕 :closed_book: 📗 :green_book: 📘 :blue_book: 📙 :orange_book: 📓 :notebook: 📔 :notebook_with_decorative_cover: 📒 :ledger: 📚 :books: 🔖 :bookmark: 📛 :name_badge: 🔬 :microscope: 🔭 :telescope: 📰 :newspaper: 🏈 :football: 🏀 :basketball: ⚽️ :soccer: ⚾️ :baseball: 🎾 :tennis: 🎱 :8ball: 🏉 :rugby_football: 🎳 :bowling: ⛳️ :golf: 🚵 :mountain_bicyclist: 🚴 :bicyclist: 🏇 :horse_racing: 🏂 :snowboarder: 🏊 :swimmer: 🏄 :surfer: 🎿 :ski: ♠️ :spades: ♥️ :hearts: ♣️ :clubs: ♦️ :diamonds: 💎 :gem: 💍 :ring: 🏆 :trophy: 🎼 :musical_score: 🎹 :musical_keyboard: 🎻 :violin: 👾 :space_invader: 🎮 :video_game: 🃏 :black_joker: 🎴 :flower_playing_cards: 🎲 :game_die: 🎯 :dart: 🀄️ :mahjong: 🎬 :clapper: 📝 :memo: 📝 :pencil: 📖 :book: 🎨 :art: 🎤 :microphone: 🎧 :headphones: 🎺 :trumpet: 🎷 :saxophone: 🎸 :guitar: 👞 :shoe: 👡 :sandal: 👠 :high_heel: 💄 :lipstick: 👢 :boot: 👕 :shirt: 👕 :tshirt: 👔 :necktie: 👚 :womans_clothes: 👗 :dress: 🎽 :running_shirt_with_sash: 👖 :jeans: 👘 :kimono: 👙 :bikini: 🎀 :ribbon: 🎩 :tophat: 👑 :crown: 👒 :womans_hat: 👞 :mans_shoe: 🌂 :closed_umbrella: 💼 :briefcase: 👜 :handbag: 👝 :pouch: 👛 :purse: 👓 :eyeglasses: 🎣 :fishing_pole_and_fish: ☕️ :coffee: 🍵 :tea: 🍶 :sake: 🍼 :baby_bottle: 🍺 :beer: 🍻 :beers: 🍸 :cocktail: 🍹 :tropical_drink: 🍷 :wine_glass: 🍴 :fork_and_knife: 🍕 :pizza: 🍔 :hamburger: 🍟 :fries: 🍗 :poultry_leg: 🍖 :meat_on_bone: 🍝 :spaghetti: 🍛 :curry: 🍤 :fried_shrimp: 🍱 :bento: 🍣 :sushi: 🍥 :fish_cake: 🍙 :rice_ball: 🍘 :rice_cracker: 🍚 :rice: 🍜 :ramen: 🍲 :stew: 🍢 :oden: 🍡 :dango: 🥚 :egg: 🍞 :bread: 🍩 :doughnut: 🍮 :custard: 🍦 :icecream: 🍨 :ice_cream: 🍧 :shaved_ice: 🎂 :birthday: 🍰 :cake: 🍪 :cookie: 🍫 :chocolate_bar: 🍬 :candy: 🍭 :lollipop: 🍯 :honey_pot: 🍎 :apple: 🍏 :green_apple: 🍊 :tangerine: 🍋 :lemon: 🍒 :cherries: 🍇 :grapes: 🍉 :watermelon: 🍓 :strawberry: 🍑 :peach: 🍈 :melon: 🍌 :banana: 🍐 :pear: 🍍 :pineapple: 🍠 :sweet_potato: 🍆 :eggplant: 🍅 :tomato: 🌽 :corn: Places 🏠 :house: 🏡 :house_with_garden: 🏫 :school: 🏢 :office: 🏣 :post_office: 🏥 :hospital: 🏦 :bank: 🏪 :convenience_store: 🏩 :love_hotel: 🏨 :hotel: 💒 :wedding: ⛪️ :church: 🏬 :department_store: 🏤 :european_post_office: 🌇 :city_sunrise: 🌆 :city_sunset: 🏯 :japanese_castle: 🏰 :european_castle: ⛺️ :tent: 🏭 :factory: 🗼 :tokyo_tower: 🗾 :japan: 🗻 :mount_fuji: 🌄 :sunrise_over_mountains: 🌅 :sunrise: 🌠 :stars: 🗽 :statue_of_liberty: 🌉 :bridge_at_night: 🎠 :carousel_horse: 🌈 :rainbow: 🎡 :ferris_wheel: ⛲️ :fountain: 🎢 :roller_coaster: 🚢 :ship: 🚤 :speedboat: ⛵️ :boat: ⛵️ :sailboat: 🚣 :rowboat: ⚓️ :anchor: 🚀 :rocket: ✈️ :airplane: 🚁 :helicopter: 🚂 :steam_locomotive: 🚊 :tram: 🚞 :mountain_railway: 🚲 :bike: 🚡 :aerial_tramway: 🚟 :suspension_railway: 🚠 :mountain_cableway: 🚜 :tractor: 🚙 :blue_car: 🚘 :oncoming_automobile: 🚗 :car: 🚗 :red_car: 🚕 :taxi: 🚖 :oncoming_taxi: 🚛 :articulated_lorry: 🚌 :bus: 🚍 :oncoming_bus: 🚨 :rotating_light: 🚓 :police_car: 🚔 :oncoming_police_car: 🚒 :fire_engine: 🚑 :ambulance: 🚐 :minibus: 🚚 :truck: 🚋 :train: 🚉 :station: 🚆 :train2: 🚅 :bullettrain_front: 🚄 :bullettrain_side: 🚈 :light_rail: 🚝 :monorail: 🚃 :railway_car: 🚎 :trolleybus: 🎫 :ticket: ⛽️ :fuelpump: 🚦 :vertical_traffic_light: 🚥 :traffic_light: ⚠️ :warning: 🚧 :construction: 🔰 :beginner: 🏧 :atm: 🎰 :slot_machine: 🚏 :busstop: 💈 :barber: ♨️ :hotsprings: 🏁 :checkered_flag: 🎌 :crossed_flags: 🏮 :izakaya_lantern: 🗿 :moyai: 🎪 :circus_tent: 🎭 :performing_arts: 📍 :round_pushpin: 🚩 :triangular_flag_on_post: Symbols 1️⃣ :one: 2️⃣ :two: 3️⃣ :three: 4️⃣ :four: 5️⃣ :five: 6️⃣ :six: 7️⃣ :seven: 8️⃣ :eight: 9️⃣ :nine: 🔟 :keycap_ten: 🔢 :1234: 0️⃣ :zero: #️⃣ :hash: 🔣 :symbols: ◀️ :arrow_backward: ⬇️ :arrow_down: ▶️ :arrow_forward: ⬅️ :arrow_left: 🔠 :capital_abcd: 🔡 :abcd: 🔤 :abc: ↙️ :arrow_lower_left: ↘️ :arrow_lower_right: ➡️ :arrow_right: ⬆️ :arrow_up: ↖️ :arrow_upper_left: ↗️ :arrow_upper_right: ⏬ :arrow_double_down: ⏫ :arrow_double_up: 🔽 :arrow_down_small: ⤵️ :arrow_heading_down: ⤴️ :arrow_heading_up: ↩️ :leftwards_arrow_with_hook: ↪️ :arrow_right_hook: ↔️ :left_right_arrow: ↕️ :arrow_up_down: 🔼 :arrow_up_small: 🔃 :arrows_clockwise: 🔄 :arrows_counterclockwise: ⏪ :rewind: ⏩ :fast_forward: ℹ️ :information_source: 🆗 :ok: 🔀 :twisted_rightwards_arrows: 🔁 :repeat: 🔂 :repeat_one: 🆕 :new: 🔝 :top: 🆙 :up: 🆒 :cool: 🆓 :free: 🆖 :ng: 🎦 :cinema: 🈁 :koko: 📶 :signal_strength: 🈂️ :sa: 🚻 :restroom: 🚹 :mens: 🚺 :womens: 🚼 :baby_symbol: 🚭 :no_smoking: 🅿️ :parking: ♿️ :wheelchair: 🚇 :metro: 🛄 :baggage_claim: 🉑 :accept: 🚾 :wc: 🚰 :potable_water: 🚮 :put_litter_in_its_place: ㊙️ :secret: ㊗️ :congratulations: Ⓜ️ :m: 🛂 :passport_control: 🛅 :left_luggage: 🛃 :customs: 🉐 :ideograph_advantage: 🆑 :cl: 🆘 :sos: 🆔 :id: 🚫 :no_entry_sign: 🔞 :underage: 📵 :no_mobile_phones: 🚯 :do_not_litter: 🚱 :non-potable_water: 🚳 :no_bicycles: 🚷 :no_pedestrians: 🚸 :children_crossing: ⛔️ :no_entry: ✳️ :eight_spoked_asterisk: ✴️ :eight_pointed_black_star: 💟 :heart_decoration: 🆚 :vs: 📳 :vibration_mode: 📴 :mobile_phone_off: 💹 :chart: 💱 :currency_exchange: ♈️ :aries: ♉️ :taurus: ♊️ :gemini: ♋️ :cancer: ♌️ :leo: ♍️ :virgo: ♎️ :libra: ♏️ :scorpius: ♐️ :sagittarius: ♑️ :capricorn: ♒️ :aquarius: ♓️ :pisces: ⛎ :ophiuchus: 🔯 :six_pointed_star: ❎ :negative_squared_cross_mark: 🅰️ :a: 🅱️ :b: 🆎 :ab: 🅾️ :o2: 💠 :diamond_shape_with_a_dot_inside: ♻️ :recycle: 🔚 :end: 🔛 :on: 🔜 :soon: 🕐 :clock1: 🕜 :clock130: 🕙 :clock10: 🕥 :clock1030: 🕚 :clock11: 🕦 :clock1130: 🕛 :clock12: 🕧 :clock1230: 🕑 :clock2: 🕝 :clock230: 🕒 :clock3: 🕞 :clock330: 🕓 :clock4: 🕟 :clock430: 🕔 :clock5: 🕠 :clock530: 🕕 :clock6: 🕡 :clock630: 🕖 :clock7: 🕢 :clock730: 🕗 :clock8: 🕣 :clock830: 🕘 :clock9: 🕤 :clock930: 💲 :heavy_dollar_sign: ©️ :copyright: ®️ :registered: ™️ :tm: ❌ :x: ❗️ :heavy_exclamation_mark: ‼️ :bangbang: ⁉️ :interrobang: ⭕️ :o: ✖️ :heavy_multiplication_x: ➕ :heavy_plus_sign: ➖ :heavy_minus_sign: ➗ :heavy_division_sign: 💮 :white_flower: 💯 :100: ✔️ :heavy_check_mark: ☑️ :ballot_box_with_check: 🔘 :radio_button: 🔗 :link: ➰ :curly_loop: 〰️ :wavy_dash: 〽️ :part_alternation_mark: 🔱 :trident: 🔻 :small_red_triangle_down: 🔺 :small_red_triangle: ✅ :white_check_mark: 🔲 :black_square_button: 🔳 :white_square_button: ⚫️ :black_circle: ⚪️ :white_circle: 🔴 :red_circle: 🔵 :large_blue_circle: 🔷 :large_blue_diamond: 🔶 :large_orange_diamond: 🔹 :small_blue_diamond: 🔸 :small_orange_diamond:","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Emoji","slug":"Emoji","permalink":"https://blog.hasaik.com/tags/Emoji/"}]},{"title":"FFmpeg + nginx-http-flv-module + flv.js 实现视频流播放","slug":"about-nginx-http-flv-module","date":"2020-04-01T09:22:38.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/358f95d9.html","link":"","permalink":"https://blog.hasaik.com/posts/358f95d9.html","excerpt":"","text":"最近项目组接了一个视频流项目,项目的主要核心是将网络摄像头RTSP视频流在WEB端实时播放,经过两天的调查和爬坑,终于实现了视频流的播放。 接下来为大家讲讲 linux 系统下搭建 nginx-http-flv-module 的全部过程。 🌞 前期准备 下载VLC(用于测试视频流是否可以播放):https://www.videolan.org/ 下载nginx包:https://nginx.org/download/nginx-1.16.1.tar.gz 下载nginx-http-flv-module 模块包:https://github.com/winshining/nginx-http-flv-module 下载FFmpeg:http://www.ffmpeg.org/releases/ffmpeg-4.2.2.tar.bz2 下载yasm(安装FFmpeg可能会报错,所以需要安装yasm):http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz 下载x264(视频转码需要用到):https://git.videolan.org/git/x264.git 下载nasm(x264编译时需要安装nasm):https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.bz2 下载完以后将所有的文件上传到 /usr/local 文件夹下面 附: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}以上百度资源链接https://pan.baidu.com/s/1gn_D5I-rJTG39YFElow4lw 提取码:ueic 🌞 搭建环境 🎉 安装nginx 在 /user/local 下创建 nginx 文件夹 1mkdir /usr/local/nginx 安装依赖项 12345yum -y install unzipyum -y install gcc-c++ yum -y install pcre pcre-devel yum -y install zlib zlib-devel yum -y install openssl openssl-devel 将 /user/local 下面的 nginx-http-flv-module-master.zip 解压到 /usr/local/nginx 下面 123cd /usr/localunzip nginx-http-flv-module-master.zip //解压文件cp -r /usr/local/nginx-http-flv-module-master /usr/local/nginx/nginx-http-flv-module //解压文件复制到/usr/local/nginx 目录下 将 nginx-http-flv-module 模板添加到 nginx 中,生成 make 文件并安装 nginx 1234567cd /usr/localtar -zxvf nginx-1.16.1.tar.gzcd nginx-1.16.1/./configure --prefix=/usr/local/nginx --add-module=/usr/local/nginx/nginx-http-flv-modulemake && make installln -s /usr/local/nginx/sbin/nginx /usr/local/bin/ //配置nginx为全局变量nginx -v //查看nginx是否安装成功 修改配置文件 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283cd /usr/local/nginx/confvi nginx.confworker_processes 1; #Nginx开启1个子进程,子进程个数最好跟CPU的核心数一样#worker_processes auto; #from versions 1.3.8 and 1.2.5#worker_cpu_affinity 0001 0010 0100 1000; #CPU的mask,子进程使用它来绑定CPU核心,避免进程切换造成性能损失#worker_cpu_affinity auto; #from version 1.9.10error_log logs/error.log error; #错误日志位置和日志级别,如果使用默认编译选项,位置为/usr/local/nginx/logs/error.log,error表示只打印错误日志events { worker_connections 4096; #Nginx处理的最大连接数}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; #Nginx监听的HTTP请求端口 server_name localhost; location / { root html; #HTTP请求URL映射到服务器的位置 index index.html index.htm; #HTTP请求优先请求的文件 } location /live { flv_live on; #当HTTP请求以/live结尾,匹配这儿,这个选项表示开启了flv直播播放功能 chunked_transfer_encoding on; #HTTP协议开启Transfer-Encoding: chunked;方式回复 add_header 'Access-Control-Allow-Origin' '*'; #添加额外的HTTP头 add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的HTTP头 } location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root /usr/local/nginx/module/nginx-http-flv-module; } location /control { rtmp_control all; } error_page 500 502 503 504 /50x.html; #如果遇到这些HTTP请求错误,Nginx返回50x.html的内容 location = /50x.html { root html; } }}rtmp_auto_push on; #因为Nginx可能开启多个子进程,这个选项表示推流时,媒体流会发布到多个子进程rtmp_auto_push_reconnect 1s;rtmp_socket_dir /tmp; #多个子进程情况下,推流时,最开始只有一个子进程在竞争中接收到数据,然后它再relay给其他子进程,他们之间通过unix domain socket传输数据,这个选项表示unixrtmp { out_queue 4096; out_cork 8; max_streams 128; #Nginx能接受的最大的推流数 timeout 15s; drop_idle_publisher 15s; log_interval 5s; log_size 1m; server { listen 1935; #Nginx监听的RTMP推流/拉流端口,可以省略,默认监听1935 server_name 127.0.0.1; application myapp { live on; #当推流时,RTMP路径中的APP(RTMP中一个概念)匹配myapp时,开启直播 record off; #不记录视频 gop_cache on; #开启GOP(Group of Picture)缓存,播放器解码时,收到一个完整的GOP才会开始播放,这个是减少播放延迟的选项 } }} 测试nginx是否好用 1234nginx -t //检查nginxnginx //启动ps -ef | grep nginx //查看nginx进程浏览器输入地址是否可以访问成功 🎉 安装FFmpeg 安装前期准备下载的nasm 12345cd /usr/localtar -jxvf nasm-2.14.02.tar.bz2 //如果解压不了的话安装一下bzip2( yum install -y bzip2 )cd nasm-2.14.02/./configuremake && make install 安装前期准备下载的yasm 12345cd /usr/localtar -zxvf yasm-1.3.0.tar.gzcd yasm-1.3.0/./configuremake && make install 安装x264(RTSP视频流转码时需要x264) 123cd /usr/local/x264./configure --enable-static --enable-sharedmake && make install 安装FFmpeg 1234567cd /usr/localtar -jxvf ffmpeg-4.2.2.tar.bz2cd ffmpeg-4.2.2/./configure --prefix=/usr/local/ffmpeg --enable-gpl --enable-libx264make && make install (FFmpeg编译时间比较长)ln -s /usr/local/ffmpeg/bin/ffmpeg /usr/local/bin //配置ffmpeg全局环境变量ffmpeg -version //查看ffmpeg是否安装成功 直接运行FFmpeg可能报如下错误(重点) 123456ffmpeg: error while loading shared libraries: libx264.so.157: cannot open shared object file: No such file or directory解决:vi /etc/ld.so.conf添加libx264.so文件位置我的是在:/usr/local/lib/ 加入到 ld.so.conf重载配置:ldconfig 🌞 推流 推流命令: 1ffmpeg -re -rtsp_transport tcp -i "rtsp://admin:[email protected]:554/h264/ch1/main/av_stream" -f flv -vcodec libx264 -vprofile baseline -acodec aac -tune zerolatency -preset ultrafast -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 "rtmp://192.168.153.204:1935/myapp/home" FFmpeg 命令详解: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}FFmpeg命令详解https://www.cnblogs.com/AllenChou/p/7048528.html x264 命令详解: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}x264 命令详解https://blog.csdn.net/ww506772362/article/details/41445481 🌞 拉流播放(flv.js) .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}flv.js百度资源链接https://pan.baidu.com/s/1DA9lhVDy9rJZkU52BuHffw 提取码:47w1 Html代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<!DOCTYPE html><html><head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <title>flv.js demo</title> <style> .mainContainer { display: block; width: 1024px; margin-left: auto; margin-right: auto; } .urlInput { display: block; width: 100%; margin-left: auto; margin-right: auto; margin-top: 8px; margin-bottom: 8px; } .centeredVideo { display: block; width: 100%; height: 576px; margin-left: auto; margin-right: auto; margin-bottom: auto; } .controls { display: block; width: 100%; text-align: center; margin-left: auto; margin-right: auto; } </style></head><body> <p class="mainContainer"> <video name="videoElement" id="videoElement" class="centeredVideo" controls muted autoplay width="1024" height="576"> Your browser is too old which doesn't support HTML5 video. </video> </p> <script src="flv.js"></script> <script> function start() { if (flvjs.isSupported()) { var videoElement = document.getElementById('videoElement'); var flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'http://192.168.153.204/live?port=1935&app=myapp&stream=home' }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); } } document.addEventListener('DOMContentLoaded', function () { start(); }); </script></body></html> 以上为 linux 下搭建视频流服务器的全部过程,如有不正,欢迎指出。","categories":[{"name":"流媒体服务","slug":"流媒体服务","permalink":"https://blog.hasaik.com/categories/%E6%B5%81%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1/"}],"tags":[{"name":"nginx-http-flv-module","slug":"nginx-http-flv-module","permalink":"https://blog.hasaik.com/tags/nginx-http-flv-module/"},{"name":"视频流播放","slug":"视频流播放","permalink":"https://blog.hasaik.com/tags/%E8%A7%86%E9%A2%91%E6%B5%81%E6%92%AD%E6%94%BE/"}]},{"title":"Vue axios 刷新 Jwt","slug":"about-vue-jwt","date":"2020-03-03T11:52:04.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/fc31ea3.html","link":"","permalink":"https://blog.hasaik.com/posts/fc31ea3.html","excerpt":"","text":"最近做项目,一个需求就是Jwt token的失效的时候,如果用户还在操作,那么希望无感刷新token,让用户的操作不受影响。 两种核心思路: 前端发起ajax请求 => 后端发现jwt已经过期,返回50014状态码 => 前端拦截响应数据,并发起刷新token的请求 => 拿到最新的jwt和refresh,保存到本地 => 拿到最新的jwt去进行刚刚未请求成功的接口 => 获取到刚刚请求的结果,覆盖第一次请求失败(状态码为50014)的响应数据 => 返回第二次请求的结果. 前端发起ajax请求 => 后端发现jwt已经过期,返回50014状态码 => 前端拦截响应数据,并发起刷新token的请求 => 后端返回了10007的状态码(这个时候代表refreshJwt也过期了,需要进行重新登录了) => 真正的过期了,需要跳转到登录界面. 新建一个refreshJwt.js文件 代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445import { getRefreshToken, setRefreshToken, setToken } from './auth'import axios from 'axios'import store from '../store'import { MessageBox } from 'element-ui'import i18n from '@/lang'/** * 刷新JWT设计思路: * * 第一种情况: 前端发起ajax请求 => 后端发现jwt已经过期,返回50014状态码 => 前端拦截响应数据,并发起刷新token的请求 => 拿到最新的jwt和refresh,保存到本地 => 拿到最新的jwt去进行刚刚未请求成功的接口 => 获取到刚刚请求的结果,覆盖第一次请求失败(状态码为50014)的响应数据 => 返回第二次请求的结果. * 第一种情况: 前端发起ajax请求 => 后端发现jwt已经过期,返回50014状态码 => 前端拦截响应数据,并发起刷新token的请求 => 后端返回了10007的状态码(这个时候代表refreshJwt也过期了,需要进行重新登录了) => 真正的过期了,需要跳转到登录界面. * @returns {Promise<void>} */export default async() => { // process.env.BASE_API是项目环境API const url = process.env.BASE_API + '/refreshJwt/' + getRefreshToken() await axios.post(url).then(res => { if (res.data.code === 10007) { // 身份过期,请重新登录 MessageBox.confirm( 'Token失效,你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { confirmButtonText: '重新登陆', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } else { // 设置token setToken(res.data.data.token) store.commit('SET_TOKEN', res.data.data.token) // 设置refreshToken setRefreshToken(res.data.data.refreshToken) store.commit('SET_REFRESH_TOKEN', res.data.data.refreshToken) } }).catch(error => { console.log(error) })} 配置拦截器 代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import axios from 'axios'import i18n from '@/lang'import { Message, MessageBox } from 'element-ui'import store from '../store'import { getToken, getRefreshToken } from './auth'import refreshJwt from './refreshJwt'let isLock = true// 创建axios实例const service = axios.create({ baseURL: process.env.BASE_API, // api 的 base_url timeout: 50000, // 请求超时时间, headers: { 'Content-Type': 'application/json;charset=utf-8' } // withCredentials : true})/** * 通用请求拦截配置 * @param {*} config */const axiosConf = (config) => { if (store.getters.token) { config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } return config}// request 拦截器service.interceptors.request.use(axiosConf, error => { console.log(error) return Promise.reject(error)})// response 拦截器service.interceptors.response.use( async response => { /** * code为非0是抛错 可结合自己业务进行修改 */ let data = {} const res = response.data const code = Number(res.code) if (code !== 0) { // 50014:Token 过期了; if (code === 50014) { if ((getRefreshToken() !== 'undefined' && getRefreshToken()) && isLock) { // 异步刷新JWT await refreshJwt() // 这里防止并发的时候造成死循环,所以要加锁 isLock = false // 刷新完成,继续之前的请求 response.config.headers['X-Token'] = getToken() const result = await axios.request(axiosConf(response.config)) if (result) { data = result.data isLock = true } } else { // 身份过期,请重新登录 MessageBox.confirm( 'Token失效,你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { confirmButtonText: '重新登陆', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } } else { // 消息提示 message(res.message) } } else { data = response.data } return data }, error => { console.log('错误信息:' + error) message('哎呀~ (ಥ﹏ಥ)网络又开小差了,请稍后刷新重试!') return Promise.reject(error) })/** * 消息提醒 * @param msg */export function message(msg) { Message({ message: msg, type: 'error', showClose: true, duration: 5 * 1000 })}export default service 小生的刷新token思路如此,如阁下有更好的思路,方便分享,请留言哦。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"},{"name":"Jwt","slug":"Jwt","permalink":"https://blog.hasaik.com/tags/Jwt/"}]},{"title":"使用SpringBoot AOP 记录操作日志、异常日志","slug":"about-springboot-aop","date":"2020-02-25T15:22:53.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/4598d3ed.html","link":"","permalink":"https://blog.hasaik.com/posts/4598d3ed.html","excerpt":"","text":"平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能;我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发生的异常进行统计,从而改进我们的项目,要是能做个功能专门来记录操作日志和异常日志那就好了, 当然我们肯定有方法来做这件事情,而且也不会很难,我们可以在需要的方法中增加记录日志的代码,和在每个方法中增加记录异常的代码,最终把记录的日志存到数据库中。听起来好像很容易,但是我们做起来会发现,做这项工作很繁琐,而且都是在做一些重复性工作,还增加大量冗余代码,这种方式记录日志肯定是不可行的。 我们以前学过Spring 三大特性,IOC(控制反转),DI(依赖注入),AOP(面向切面),那其中AOP的主要功能就是将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。今天我们就来用SpringBoot Aop 来做日志记录,好了,废话说了一大堆还是上货吧。 💥 创建日志记录表、异常日志表,表结构如下: 异常日志表 操作日志表 💥 添加Maven依赖 12345678910<!-- Spring Boot 面向切面AOP --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version></dependency> 💥 创建操作日志注解类Log.java 1234567891011121314151617181920package com.scaffolding.demo.annotation;import java.lang.annotation.*;/** * 自定义操作日志注解 * * @author: Xuxu * @date: 2020-02-17 18:35项目日志 **/@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行@Documentedpublic @interface Log { String operationModule() default ""; // 操作模块 String operationType() default ""; // 操作类型 String operationDesc() default ""; // 操作说明} 💥 创建切面类记录操作日志和异常日志 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225package com.scaffolding.demo.config;import com.alibaba.fastjson.JSON;import com.scaffolding.demo.annotation.Log;import com.scaffolding.demo.common.BaseController;import com.scaffolding.demo.sys.model.ExceptionLog;import com.scaffolding.demo.sys.model.OperationLog;import com.scaffolding.demo.sys.service.ExceptionLogService;import com.scaffolding.demo.sys.service.OperationLogService;import com.scaffolding.demo.sys.service.UserService;import com.scaffolding.demo.utils.IPUtil;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.core.ParameterNameDiscoverer;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestAttributes;import org.springframework.web.context.request.RequestContextHolder;import javax.servlet.http.HttpServletRequest;import java.lang.reflect.Method;import java.time.LocalDateTime;import java.util.HashMap;import java.util.Map;import java.util.Objects;/** * 切面处理类,操作日志 异常日志记录处理 * * @author: Xuxu * @date: 2020-02-17 18:46 **/@Slf4j@Aspect@Componentpublic class OperationLogAspect extends BaseController { /** * 操作版本号 */ @Value("${app.version}") private String version; @Autowired private OperationLogService operationLogService; @Autowired private ExceptionLogService exceptionLogService; @Autowired private UserService userService; /** * 设置操作日志切入点 记录操作日志 在注解的位置切入代码 */ @Pointcut("@annotation(com.scaffolding.demo.annotation.Log)") public void operationLogPointCut() { } /** * 设置操作异常切入点记录异常日志 扫描所有controller包下操作 */ @Pointcut("execution(* com.scaffolding.demo.sys.controller..*.*(..))") public void operationExceptionLogPointCut() { } /** * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行 * * @param joinPoint 切入点 * @param results 返回结果 */ @AfterReturning(value = "operationLogPointCut()", returning = "results") public void saveOperationLog(JoinPoint joinPoint, Object results) { log.info("开始执行拦截用户操作日志"); // 获取RequestAttributes RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); // 从获取RequestAttributes中获取HttpServletRequest的信息 HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes) .resolveReference(RequestAttributes.REFERENCE_REQUEST); OperationLog operationLog = new OperationLog(); try { // 从切面织入点处通过反射机制获取织入点处的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取切入点所在的方法 Method method = signature.getMethod(); // 获取操作 Log log = method.getAnnotation(Log.class); if (log != null) { // 操作模块 operationLog.setModule(log.operationModule()); // 操作类型 operationLog.setType(log.operationType()); // 操作描述 operationLog.setDescription(log.operationDesc()); } // 获取请求的类名 String className = joinPoint.getTarget().getClass().getName(); // 获取请求的方法名 String methodName = method.getName(); // 请求方法 operationLog.setMethod(className + "." + methodName); // 请求的参数 Map<String, Object> rtnMap = convertMap(joinPoint, method); // 将参数所在的数组转换成json String params = JSON.toJSONString(rtnMap); // 请求参数 operationLog.setRequestParam(params); // 返回结果 operationLog.setResponseParam(JSON.toJSONString(results)); // 请求用户ID operationLog.setUserId(getUserId()); // 请求用户名称 operationLog.setUserName(userService.getById(getUserId()).getNickName()); // 请求IP operationLog.setIp(IPUtil.getRequestIpAddress(request)); // 请求URI operationLog.setUri(request.getRequestURI()); // 创建时间 operationLog.setCreateTime(LocalDateTime.now()); // 操作版本 operationLog.setVersion(version); operationLogService.save(operationLog); } catch (Exception e) { log.error("拦截用户操作日志异常"); e.printStackTrace(); } } /** * 异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行 * * @param joinPoint 切入点 * @param exception 异常信息 */ @AfterThrowing(pointcut = "operationExceptionLogPointCut()", throwing = "exception") public void saveExceptionLog(JoinPoint joinPoint, Throwable exception) { log.info("开始拦截异常日志信息"); // 获取RequestAttributes RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); // 从获取RequestAttributes中获取HttpServletRequest的信息 HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes) .resolveReference(RequestAttributes.REFERENCE_REQUEST); ExceptionLog exceptionLog = new ExceptionLog(); try { // 从切面织入点处通过反射机制获取织入点处的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取切入点所在的方法 Method method = signature.getMethod(); // 获取请求的类名 String className = joinPoint.getTarget().getClass().getName(); // 获取请求的方法名 String methodName = method.getName(); // 请求的参数 Map<String, Object> rtnMap = convertMap(joinPoint, method); // 将参数所在的数组转换成json String params = JSON.toJSONString(rtnMap); // 请求参数 exceptionLog.setRequestParam(params); // 请求方法名 exceptionLog.setMethod(className + "." + methodName); // 异常名称 exceptionLog.setName(exception.getClass().getName()); // 异常信息 exceptionLog.setMessage(stackTraceToString(exception.getClass().getName(), exception.getMessage(), exception.getStackTrace())); // 操作员ID exceptionLog.setUserId(getUserId()); // 操作员名称 exceptionLog.setUserName(userService.getById(getUserId()).getNickName()); // 操作URI exceptionLog.setUri(request.getRequestURI()); // 操作员IP exceptionLog.setIp(IPUtil.getRequestIpAddress(request)); // 操作版本号 exceptionLog.setVersion(version); // 发生异常时间 exceptionLog.setCreateTime(LocalDateTime.now()); exceptionLogService.save(exceptionLog); } catch (Exception e) { log.error("拦截异常日志信息发生异常"); e.printStackTrace(); } } /** * 转换request 请求参数 * * @param joinPoint * @param method * @return */ private Map<String, Object> convertMap(JoinPoint joinPoint, Method method) { // 参数值 Object[] args = joinPoint.getArgs(); ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); Map<String, Object> paramMap = new HashMap<>(); for (int i = 0; i < Objects.requireNonNull(parameterNames).length; i++) { paramMap.put(parameterNames[i], args[i].toString()); } return paramMap; } /** * 转换异常信息为字符串 * * @param exceptionName 异常名称 * @param exceptionMessage 异常信息 * @param elements 堆栈信息 */ private String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) { StringBuffer stringBuffer = new StringBuffer(); for (StackTraceElement stet : elements) { stringBuffer.append(stet + "\\n"); } return exceptionName + ":" + exceptionMessage + "\\n\\t" + stringBuffer.toString(); }} 附上获取IP地址Util类: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354package com.scaffolding.demo.utils;import org.apache.commons.lang3.StringUtils;import javax.servlet.http.HttpServletRequest;import java.net.InetAddress;import java.net.UnknownHostException;/** * @author: Xuxu * @date: 2020-02-17 20:05 **/public class IPUtil { /** * 获取请求IP * * @param request * @return */ public static String getRequestIpAddress(HttpServletRequest request) { String ipAddress = null; ipAddress = request.getHeader("X-Real_IP"); if (StringUtils.isEmpty(ipAddress) || StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("x-forwarded-for"); } if (StringUtils.isEmpty(ipAddress) || StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ipAddress) || StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ipAddress) || StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals("127.0.0.1")) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } return ipAddress; }} 💥 在Controller层方法添加@Log注解 💥 操作日志、异常日志查询功能","categories":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/categories/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/tags/SpringBoot/"},{"name":"AOP","slug":"AOP","permalink":"https://blog.hasaik.com/tags/AOP/"}]},{"title":"vue项目中,js根据文件名后缀,判断文件图片、视频、文档、pdf等类型的方法","slug":"about-vue-matchType","date":"2020-02-13T17:23:42.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/dc877e7a.html","link":"","permalink":"https://blog.hasaik.com/posts/dc877e7a.html","excerpt":"","text":"vue项目中,在获得文件名信息,需要根据文件名的后缀来区分文件类型的方法如下:其中,文件后缀可自由拓展。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394// 根据文件名后缀区分 文件类型export function matchType(fileName) { // 后缀获取 let suffix = '' // 获取类型结果 let result try { const fileArr = fileName.split('.') suffix = fileArr[fileArr.length - 1] } catch (err) { suffix = '' } // fileName无后缀返回 false if (!suffix) { result = false return result } // 图片格式 const imgList = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'svg', 'icon', 'ico'] // 进行图片匹配 result = imgList.some(function(item) { return item === suffix }) if (result) { result = 'image' return result } // 匹配txt const txtList = ['txt'] result = txtList.some(function(item) { return item === suffix }) if (result) { result = 'txt' return result } // 匹配 excel const excelList = ['xls', 'xlsx'] result = excelList.some(function(item) { return item === suffix }) if (result) { result = 'excel' return result } // 匹配 word const wordList = ['doc', 'docx'] result = wordList.some(function(item) { return item === suffix }) if (result) { result = 'word' return result } // 匹配 pdf const pdfList = ['pdf'] result = pdfList.some(function(item) { return item === suffix }) if (result) { result = 'pdf' return result } // 匹配 ppt const pptList = ['ppt', 'pptx'] result = pptList.some(function(item) { return item === suffix }) if (result) { result = 'ppt' return result } // 匹配 视频 const videoList = ['mp4', 'm2v', 'mkv'] result = videoList.some(function(item) { return item === suffix }) if (result) { result = 'video' return result } // 匹配 音频 const radioList = ['mp3', 'wav', 'wmv'] result = radioList.some(function(item) { return item === suffix }) if (result) { result = 'radio' return result } // 其他 文件类型 result = 'other' return result} 在项目中进行调用,只需要在需要使用的地方使用: 1this.matchType('demo.png'); // 返回的结果为 'image' 根据返回结果可做对应操作。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"vue、react等单页应用在微信浏览器中修改标题","slug":"about-vue-site-title","date":"2020-02-05T12:07:51.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/a63fb3ae.html","link":"","permalink":"https://blog.hasaik.com/posts/a63fb3ae.html","excerpt":"","text":"由于单页应用是通过路由切换展示不同页面的,而不是真正的跳转链接,然后在IOS系统,微信浏览器中直接用修改title的值不会有效果,所以需要使用特殊方式来修改微信标题,一言不合就上代码: 1234567891011121314151617181920/*** 微信浏览器中设置对应页面的标题* 解决:IOS微信浏览器中用document.title 设置标题无效* */export const setTitle = (title) => { var body = document.getElementsByTagName('body')[0]; document.title = title; var iframe = document.createElement("iframe"); iframe.setAttribute("src", "logo.png"); iframe.setAttribute("style", "display:none"); iframe.addEventListener('load', function() { setTimeout(function() { try{ iframe.removeEventListener('load'); }catch (err){} document.body.removeChild(iframe); }, 0); }); document.body.appendChild(iframe);};","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"Vue 日期格式化","slug":"about-vue-date","date":"2020-01-15T09:39:22.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/c9b4cff2.html","link":"","permalink":"https://blog.hasaik.com/posts/c9b4cff2.html","excerpt":"","text":"函数封装(将该函数封装成一个文件,或者加入自己项目的函数库) 123456789101112131415161718192021222324// 这个函数网上随处可见,我也是应用了别人的。export function formatDate(date, fmt) { if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) } const o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds() } for (const k in o) { if (new RegExp(`(${k})`).test(fmt)) { const str = o[k] + '' fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)) } } return fmt}function padLeftZero(str) { return ('00' + str).substr(str.length)} 文件引入(注意:由于是函数,故名字要和函数的名字一致) 1import { formatDate } from '@/common/commonUtil.js' 添加到过滤器中 123456filters: { formatDate(time) { var date = new Date(time); return formatDate(date, 'yyyy-MM-dd hh:mm:ss'); }}, 使用场景一: 在HTML中使用 12// item.createDate是后台数据~~<div class="time">{{item.createDate | formatDate}}</div> 使用场景二: 在提交时候使用 1let nowDate = formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss') 使用场景三: 在绑定属性中使用 1<mt-cell title="开始时间" :value="startDate | formatDate"></mt-cell>","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"}]},{"title":"异常:java.security.InvalidKeyException:Illegal key size","slug":"about-java-IllegalKeySize","date":"2020-01-07T10:13:18.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/2b56997e.html","link":"","permalink":"https://blog.hasaik.com/posts/2b56997e.html","excerpt":"","text":"今天在做接口测试的时候遇到个异常: java.security.InvalidKeyException: Illegal key size 123456KeyGenerator kgen = KeyGenerator.getInstance("aes");//此处解决mac,linux报错SecureRandom random = SecureRandom.getInstance("SHA1PRNG");random.setSeed(key.getBytes()); //mac笔记本当代码运行到这一行时就报错了。爆出上面的异常kgen.init(type.value, random);SecretKey secretKey = kgen.generateKey(); 感到一脸懵逼,还好网络是万能的,百度一下,简单对比一下,就找到了解决方案。然后测试之后发现也是没有问题的。 异常原因:如果密钥大于128, 会抛出java.security.InvalidKeyException: Illegal key size 异常. 因为密钥长度是受限制的, java运行时环境读到的是受限的policy文件. 文件位于${java_home}/jre/lib/security, 这种限制是因为美国对软件出口的控制. 解决方案:去官方下载JCE无限制权限策略文件。 JDK5的下载地址: http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-java-plat-419418.html#jce_policy-1.5.0-oth-JPR JDK6的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html JDK8的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt 如果安装了JRE,将两个jar文件放到%JRE_HOME%\\lib\\security目录下覆盖原来的文件 如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\\jre\\lib\\security目录下覆盖原来文件。 然后DuangDuangDuangDuang,就ok了。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"BUG","slug":"Java/BUG","permalink":"https://blog.hasaik.com/categories/Java/BUG/"}],"tags":[{"name":"BUG","slug":"BUG","permalink":"https://blog.hasaik.com/tags/BUG/"}]},{"title":"Element UI DatePicker 禁用当前日之前的时间","slug":"about-vue-datepicker","date":"2020-01-06T14:00:20.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/69ab28bb.html","link":"","permalink":"https://blog.hasaik.com/posts/69ab28bb.html","excerpt":"","text":"这篇文章主要介绍了 vue element-ui el-date-picker 禁用当日之前时间,文中给大家提供了代码段和截图,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下。 1234567<el-date-picker v-model="timingDay" :picker-options="expireTimeOption" format="yyyy-MM-dd" type="date" value-format="yyyy-MM-dd" placeholder="选择时间"/> 其中 :picker-options="expireTimeOption" 便是限制选择时间的属性,在data中可以这样写 123456789data:{ return{ timingDay: '', expireTimeOption: { disabledDate(date) { return date.getTime() < Date.now() - 8.64e7 } }} 这样便实现了禁用当日之前时间 以上所述是小编给大家介绍的vue element-ui el-date-picker限制选择时间为当天之前的代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"},{"name":"el-date-picker","slug":"el-date-picker","permalink":"https://blog.hasaik.com/tags/el-date-picker/"}]},{"title":"Vue中Axios的封装与使用","slug":"about-vue-axios","date":"2019-12-04T10:05:38.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/4394a738.html","link":"","permalink":"https://blog.hasaik.com/posts/4394a738.html","excerpt":"","text":"Axios是什么,为什么要统一封装? axios 是一个基于 promise 的 http 库,可运行在浏览器端和 node.js 中。他有很多优秀的特性,例如统一进行拦截请求和响应、取消请求、转换 json 、客户端防御 XSRF 等。所以在日常开发中可以直接推荐我们使用 axios 库。如果还对 axios 不了解的,可以移步 axios文档。回归正题,我们所要的说的 axios 的封装和 api 接口的统一管理,其实主要目的就是在帮助我们简化代码和利于后期的更新维护。 安装 使用 npm: 1$ npm install axios 使用 bower: 1$ bower install axios 使用 cdn: 1<script src="https://unpkg.com/axios/dist/axios.min.js"></script> 统一封装Axios拦截器 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495import axios from 'axios' // 引入axiosimport { Message, MessageBox } from 'element-ui' // 这里我是使用elementUI的组件来给提示import store from '../store' //引入Vuex的Storeimport { getToken } from '@/utils/auth' //一个获取cookie中token的工具类import Cookies from 'js-cookie' //引入cookie// 创建axios实例,在这里可以设置请求的默认配置const service = axios.create({ baseURL: process.env.BASE_API, //根据自己配置的反向代理去设置不同环境的baeUrl timeout: 50000, // 请求超时时间, headers: { 'Content-Type': 'application/json;charset=utf-8' } // withCredentials : true})// request 拦截器(言外之意就是在发起请求前做什么)service.interceptors.request.use( config => { if (store.getters.token) { config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } return config }, error => { // 对请求错误做些什么 console.log(error) // for debug return Promise.reject(error) })// response 拦截器service.interceptors.response.use( response => { /** * code为非0是抛错 可结合自己业务进行修改 */ let message let title let confirmButtonText let cancelButtonText const res = response.data if (res.code !== 0) { Message({ message: res.message, type: 'error', duration: 5 * 1000 }) // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; if (res.code === 50008 || res.code === 50012 || res.code === '50014' || res.code === '333') { if (Cookies.get('language') === 'zh') { message = '登录超时,请重新登录!' title = '确定登出' confirmButtonText = '重新登录' cancelButtonText = '取消' } MessageBox.confirm( message, title, { confirmButtonText: confirmButtonText, cancelButtonText: cancelButtonText, type: 'warning', showCancelButton: false } ).then(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }).catch(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } return Promise.reject(new Error(res.message || 'Error')) } else { console.log(response) return response.data } }, error => { console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) })export default service 统一进行接口api管理 12345678910// 每个模块都应该有自己的接口文件去统一管理apiimport request from '@/utils/request'export function login(data) { return request({ url: '/login', method: 'post', data: data })} 页面上的使用 1234567891011121314import { login } from '@/api/login'export default { name: 'login', data () { return {} }, mounted () { let params = { userName: 'admin', password: '123456'} login(params).then(res => { console.log(res, '这是响应的结果') }) }} 我的项目目录结构 总结 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家的支持。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"},{"name":"Axios","slug":"Axios","permalink":"https://blog.hasaik.com/tags/Axios/"}]},{"title":"为Hexo添加RSS订阅","slug":"about-hexo-rss","date":"2019-11-29T16:04:03.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/19c94341.html","link":"","permalink":"https://blog.hasaik.com/posts/19c94341.html","excerpt":"","text":"配置 首先添加功能插件,在 hexo 项目根目录下执行该命令 1npm install hexo-generator-feed --save 然后在 hexo 根目录下的 _config.yml 文件中添加配置 12345#订阅RSSfeed: type: atom path: atom.xml limit: false 配置含义: type: RSS的类型(atom/rss2) path: 文件路径,默认是 atom.xml/rss2.xml limit: 展示文章的数量,使用 0 或则 false 代表展示全部 hub: URL of the PubSubHubbub hubs (如果使用不到可以为空) content: (可选)设置 true 可以在 RSS 文件中包含文章全部内容,默认:false content_limit: (可选)摘要中使用的帖子内容的默认长度。 仅在内容设置为false且未显示自定义帖子描述时才使用。 content_limit_delim: (可选)如果content_limit用于缩短post内容,则仅在此分隔符的最后一次出现时进行剪切,然后才达到字符限制。默认不使用。 icon: (可选)自定义订阅图标,默认设置为主配置中指定的图标。 order_by: 订阅内容的顺序。 (默认: -date) 然后在 theme 目录下的 _config.yml 文件中添加配置 1rss: /atom.xml 随后重新生成博客静态文件 1$ hexo clean && hexo g 在 public 文件夹中就会生成 atom.xml 文件,部署后直接在根目录中访问该文件即可 1https://hasaik.com/atom.xml 订阅 下面以我博客为例子 订阅地址为:https://hasaik.com/atom.xml(PS:订阅地址改为自己的博客) 以上就是关于博客 RSS 订阅的全部介绍,如果您喜欢我发布的文章,亦可订阅小站,小站将第一时间为您奉上新发布的文章。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Rss","slug":"Rss","permalink":"https://blog.hasaik.com/tags/Rss/"}]},{"title":"SpringBoot两种打包方式","slug":"about-springboot-deploy","date":"2019-11-29T10:00:38.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/b4831a5e.html","link":"","permalink":"https://blog.hasaik.com/posts/b4831a5e.html","excerpt":"","text":"相信所有人都喜欢简洁的打包方式,不需要去敲命令来执行打包,所以今天介绍两种常用的打包方式。 Maven插件打包 在项目 pom.xml 文件中 build 标签的代码为朋友们奉上,其中的注意的点都有注释 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172<build> <!--打包项目名(根据自己项目定)--> <finalName>contests</finalName> <plugins> <!-- 设置jdk版本为1.8 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <compilerArguments> <!--如果是在Windows下面开发,${java.home}/lib/rt.jar ; ${java.home,这里中间是;号隔开,Linux中则是:号隔开,这是个很坑的地方我提一下。--> <bootclasspath>${java.home}/lib/rt.jar;${java.home}/lib/jce.jar</bootclasspath> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 --> <outputDirectory>${project.build.directory}/${project.name}</outputDirectory> <addResources>false</addResources> <includeSystemScope>true</includeSystemScope> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <encoding>UTF-8</encoding> <outputDirectory> ${project.build.directory}/${project.name} </outputDirectory> <!-- 表示把配置文件拷到和jar包同一个路径下 --> <resources> <resource> <directory>src/main/resources/</directory> </resource> </resources> </configuration> </execution> <execution> <id>copy-resources-classes</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <encoding>UTF-8</encoding> <outputDirectory> ${project.build.directory}/classes </outputDirectory> <!-- 表示把配置文件拷到和jar包同一个路径下 --> <resources> <resource> <directory>src/main/resources/</directory> </resource> </resources> </configuration> </execution> </executions> </plugin> </plugins></build> 在 pom.xml 中有一个这样的地方: 12345<properties> <java.version>1.8</java.version> <!-- maven打包跳过测试阶段(有的数据库连接的是本地的,不是服务器的,打包过程会出现连接数据库失败的错误,设置跳过测试阶段就解决了,不影响发布) --> <skipTests>true</skipTests></properties> 在 pom.xml 中 <packaging>war</packaging> 是选择打包的类型(war,jar) 配置好 pom.xml 之后,一定要是 Jdk1.8 ,在IDEA的右上角,有个 Maven Project。 先点Clean,然后点package然后项目目录多了个target文件夹,里面就生成了你要的jar包了,现在就可以去部署在服务器啦。 IDEA自带打包 先在你项目的启动类中加入以下代码: 12345678910/** * springboot打包发布到tomcat需要 * * @param application * @return */@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(DemoApplication.class);} 接下来快捷键 Ctrl+Alt+Shift+S 同时按会出现下图页面 点击 + 号!!!然后选择如图所示的 Empty 出现如下界面 设置完成后点击 OK。 接下来执行 Bulid ,选择你新建的打包方式名执行就 OK 啦。 以上就是介绍的两种打包方式,自行选择使用。","categories":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/categories/SpringBoot/"}],"tags":[{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/tags/SpringBoot/"},{"name":"SpringBoot打包","slug":"SpringBoot打包","permalink":"https://blog.hasaik.com/tags/SpringBoot%E6%89%93%E5%8C%85/"}]},{"title":"本站已开通订阅功能","slug":"about-hexo-subscribe","date":"2019-11-25T11:19:18.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/dd9d9f86.html","link":"","permalink":"https://blog.hasaik.com/posts/dd9d9f86.html","excerpt":"","text":"小站新开通订阅功能,欢迎大家体验。 123456789101112131415161718 .::::. .::::::::. ::::::::::: ..:::::::::::' '::::::::::::' .:::::::::: '::::::::::::::.. ..::::::::::::. ``:::::::::::::::: ::::``:::::::::' .:::. ::::' ':::::' .::::::::. .::::' :::: .:::::::'::::. .:::' ::::: .:::::::::' ':::::. .::' :::::.:::::::::' ':::::. .::' ::::::::::::::' ``::::. ...::: ::::::::::::' ``::.````':. ':::::::::' ::::.. '.:::::' ':'````.. 订阅地址: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}订阅地址https://mailchi.mp/3ca18a2a9749/xuxuy 佛祖保佑永无BUG 神兽护体 代码注释(各种版本): .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}佛祖保佑、永无BUGhttps://blog.csdn.net/vbirdbest/article/details/78995793","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"邮件订阅","slug":"邮件订阅","permalink":"https://blog.hasaik.com/tags/%E9%82%AE%E4%BB%B6%E8%AE%A2%E9%98%85/"}]},{"title":"Hexo优化之lazyload图片懒加载","slug":"about-hexo-lazyload","date":"2019-11-19T14:54:16.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/61913369.html","link":"","permalink":"https://blog.hasaik.com/posts/61913369.html","excerpt":"","text":"前言 Hexo 博客虽然功能很强大,但也越来越繁重了,访问速度上有了一些问题,这里我也考虑了许多,例如加 cdn,将国外的资源引用改为国内镜像等方式。今天又想到如果一个页面的图片很多,那么如何来提高博客的访问速度呢? 经过一番寻找之后,找到一个方案,就是懒加载,通俗点讲就是当你翻到图片的时候再加载那张图片,而不是以下将本页面的所有图片都加载完。 配置 在你的 Hexo 目录下,执行以下命令: 1npm install hexo-lazyload-image --save 然后在你的 Hexo 目录的配置文件 _config.yml 中添加配置: 1234lazyload: enable: true onlypost: false loadingImg: /images/loading.gif 注意 onlypost:是否仅文章中的图片做懒加载,如果为 false,则主题中的其他图片,也会做懒加载,如头像,logo 等任何图片。 loadingImg:图片未加载时的代替图,不填写使用默认加载图片,如果需要自定义,添填入 loading 图片地址,如果是本地图片,不要忘记把图片添加到你的主题目录下。 Next 主题需将图片放到 \\themes\\next\\source\\images 目录下,然后引用时:loadingImg: /images/图片文件名 福利 送上两个gif加载中动图 点击下载动图1 点击下载动图2","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"lazyload","slug":"lazyload","permalink":"https://blog.hasaik.com/tags/lazyload/"},{"name":"图片","slug":"图片","permalink":"https://blog.hasaik.com/tags/%E5%9B%BE%E7%89%87/"}]},{"title":"Java base64加密解密","slug":"about-java-base64","date":"2019-11-19T13:35:23.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/c4782247.html","link":"","permalink":"https://blog.hasaik.com/posts/c4782247.html","excerpt":"","text":"为什么要使用Base64 Base 64主要用途不是加密,而是把一些二进制数转成普通字符,方便在网络上传输。 由于一些二进制字符在传输协议中属于控制字符,不能直接传送,所以需要转换一下才可以。由于某些系统中只能使用ASCII字符,Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法,Base64特别适合在http,mime协议下快速传输数据。比如网络中图片的传输。 Base64并非安全领域下的加密解密算法。虽然经常遇到所谓的base64的加密解密。但base64只能算是一个编码算法,对数据内容进行编码来适合传输。虽然base64编码过后原文也变成不能看到的字符格式,但是方式初级又简单。 Base64原理 Base64编码方法,要求把每三个8Bit的字节转换为四个6Bit的字节,其中,转换之后的这四个字节中每6个有效bit为是有效数据,空余的那两个 bit用0补上成为一个字节。因此Base64所造成数据冗余不是很严重,Base64是当今比较流行的编码方法,因为它编起来速度快而且简单。 举个例子,有三个字节的原始数据:aaaaaabb bbbbccccc ccdddddd(这里每个字母表示一个bit位) 那么编码之后会变成:00aaaaaa 00bbbbbb 00cccccc 00dddddd 所以可以看出base64编码简单,虽然编码后不是明文,看不出原文,但是解码也很简单。 实现方式 123456789101112131415161718192021222324252627282930313233package com.scaffolding.demo.utils;import java.nio.charset.StandardCharsets;import java.util.Base64;/** * @author: Xuxu * @date: 2019-11-19 11:49 **/public class Base64Util { /** * 加密 */ public static String asBase64(String str) { return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)); } /** * 解密 */ public static String asString(String base64) { return new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8); } public static void main(String[] args) { String str = "123456"; //加密 System.out.println(Base64Util.asBase64(str)); //解密后 System.out.println(Base64Util.asString(Base64Util.asBase64(str))); }}","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"加密安全","slug":"Java/加密安全","permalink":"https://blog.hasaik.com/categories/Java/%E5%8A%A0%E5%AF%86%E5%AE%89%E5%85%A8/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"Base64","slug":"Base64","permalink":"https://blog.hasaik.com/tags/Base64/"}]},{"title":"Java实现MD5加盐加密和MD5与SHA-1混合加盐加密两种方式","slug":"about-java-md5","date":"2019-11-18T12:53:11.000Z","updated":"2021-03-25T13:28:10.069Z","comments":true,"path":"posts/a7df3b40.html","link":"","permalink":"https://blog.hasaik.com/posts/a7df3b40.html","excerpt":"","text":"现在一般的MD5加密在网上随随便便就能够解密,解密的网站有以下几个: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}PMD5http://pmd5.com/ .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}CMD5http://www.cmd5.com/ .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}站长工具http://tool.chinaz.com/tools/md5.aspx 好了介绍了这么多密码解密的网站,现在我们来介绍如何提高密码的安全性,来防止上面的网站轻松破解我们的密码。 MD5加盐加密 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104package com.scaffolding.demo.utils;import java.security.MessageDigest;import java.util.Random;/** * @author: Xuxu * @date: 2019-11-18 11:20 **/public class MD5Util { /** * byte[]字节数组 转换成 十六进制字符串 * * @param arr 要转换的byte[]字节数组 * @return String 返回十六进制字符串 */ private static String hex(byte[] arr) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < arr.length; ++i) { sb.append(Integer.toHexString((arr[i] & 0xFF) | 0x100).substring(1, 3)); } return sb.toString(); } /** * MD5加密,并把结果由字节数组转换成十六进制字符串 * * @param str 要加密的内容 * @return String 返回加密后的十六进制字符串 */ private static String md5Hex(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(str.getBytes()); return hex(digest); } catch (Exception e) { e.printStackTrace(); System.out.println(e.toString()); return ""; } } /** * 生成含有随机盐的密码 * * @param password 要加密的密码 * @return String 含有随机盐的密码 */ public static String getSaltMD5(String password) { // 生成一个16位的随机数 Random random = new Random(); StringBuilder sBuilder = new StringBuilder(16); sBuilder.append(random.nextInt(99999999)).append(random.nextInt(99999999)); int len = sBuilder.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { sBuilder.append("0"); } } // 生成最终的加密盐 String salt = sBuilder.toString(); password = md5Hex(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return String.valueOf(cs); } /** * 验证加盐后是否和原密码一致 * * @param password 原密码 * @param password 加密之后的密码 * @return boolean true表示和原密码一致 false表示和原密码不一致 */ public static boolean getSaltverifyMD5(String password, String md5str) { char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 48; i += 3) { cs1[i / 3 * 2] = md5str.charAt(i); cs1[i / 3 * 2 + 1] = md5str.charAt(i + 2); cs2[i / 3] = md5str.charAt(i + 1); } String Salt = new String(cs2); return md5Hex(password + Salt).equals(String.valueOf(cs1)); } public static void main(String[] args) { // 原密码 String plaintext = "123456"; // 获取加盐后的MD5值 String ciphertext = MD5Util.getSaltMD5(plaintext); System.out.println("加盐后MD5:" + ciphertext); System.out.println("是否是同一字符串:" + MD5Util.getSaltverifyMD5(plaintext, ciphertext)); }} 输出结果为: 加盐后MD5:e9a97f49db0f20911ab1d815624b33e75a3236e76040f509 是否是同一字符串:true 这时,我们可以把加盐后的 加密密码 拿到 MD5加密网上去验证是否能够解密(这里我只列举其中一个网站进行验证,你们也可以自行拿去各个MD5加密网站上去验证) 我们可以看到,MD5加密网站已经无法破解我们加密的密码了,所以MD5加盐加密的密码相对来说还是比较安全的。 MD5和SHA-1混合加盐加密 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152package com.scaffolding.demo.utils;import java.io.UnsupportedEncodingException;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.Random;/** * @author: Xuxu * @date: 2019-11-18 13:15 **/public class MD5Utils { /** * md5和sha-1混合加密 * * @param inputText 要加密的内容 * @return String md5和sha-1混合加密之后的密码 */ public static String md5AndSha(String inputText) { return sha(md5(inputText)); } /** * md5加密 * * @param inputText 要加密的内容 * @return String md5加密之后的密码 */ public static String md5(String inputText) { return encrypt(inputText, "md5"); } /** * sha-1加密 * * @param inputText 要加密的内容 * @return sha-1加密之后的密码 */ public static String sha(String inputText) { return encrypt(inputText, "sha-1"); } /** * md5或者sha-1加密 * * @param inputText 要加密的内容 * @param algorithmName 加密算法名称:md5或者sha-1,不区分大小写 * @return String md5或者sha-1加密之后的结果 */ private static String encrypt(String inputText, String algorithmName) { if (inputText == null || "".equals(inputText.trim())) { throw new IllegalArgumentException("请输入要加密的内容"); } if (algorithmName == null || "".equals(algorithmName.trim())) { algorithmName = "md5"; } String encryptText = null; try { MessageDigest m = MessageDigest.getInstance(algorithmName); m.update(inputText.getBytes("UTF8")); byte s[] = m.digest(); return hex(s); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return encryptText; } /** * byte[]字节数组 转换成 十六进制字符串 * * @param arr 要转换的byte[]字节数组 * @return String 返回十六进制字符串 */ private static String hex(byte[] arr) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < arr.length; ++i) { sb.append(Integer.toHexString((arr[i] & 0xFF) | 0x100).substring(1, 3)); } return sb.toString(); } /** * 生成含有随机盐的密码 * * @param password 要加密的密码 * @return String 含有随机盐的密码 */ public static String getSaltMd5AndSha(String password) { // 生成一个16位的随机数 Random random = new Random(); StringBuilder sBuilder = new StringBuilder(16); sBuilder.append(random.nextInt(99999999)).append(random.nextInt(99999999)); int len = sBuilder.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { sBuilder.append("0"); } } // 生成最终的加密盐 String salt = sBuilder.toString(); password = md5AndSha(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return String.valueOf(cs); } /** * 验证加盐后是否和原密码一致 * * @param password 原密码 * @param password 加密之后的密码 * @return boolean true表示和原密码一致 false表示和原密码不一致 */ public static boolean getSaltverifyMd5AndSha(String password, String md5str) { char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 48; i += 3) { cs1[i / 3 * 2] = md5str.charAt(i); cs1[i / 3 * 2 + 1] = md5str.charAt(i + 2); cs2[i / 3] = md5str.charAt(i + 1); } String salt = new String(cs2); String encrypPassword = md5AndSha(password + salt); // 加密密码去掉最后8位数 encrypPassword = encrypPassword.substring(0, encrypPassword.length() - 8); return encrypPassword.equals(String.valueOf(cs1)); } public static void main(String[] args) { // 原密码 String plaintext = "123456"; // 获取加盐后的MD5值 String ciphertext = MD5Utils.getSaltMd5AndSha(plaintext); System.out.println("加盐后MD5:" + ciphertext); System.out.println("是否是同一字符串:" + MD5Utils.getSaltverifyMd5AndSha(plaintext, ciphertext)); }} 眼睛比较明亮的朋友,可能会发现 MD5 和 SHA-1 混合加盐加密 与 MD5 加盐加密 的 getSaltverifyMD5(String password, String md5str) 方法有些不同,是的,MD5和SHA-1混合加盐加密 的 getSaltverifyMD5(String password, String md5str) 多了下面这一行代码: 12// 加密密码去掉最后8位数encrypPassword = encrypPassword.substring(0 , encrypPassword.length() - 8); 就会有人问了,为什么要去掉加密密码的最后8位数,而MD5加盐加密却不要?其实这是有原因的 我们可以看到密码为 123456 经过 MD5、MD5和SHA-1 混合加密的结果: MD5 :e10adc3949ba59abbe56e057f20f883e (32位数) MD5和SHA-1 :10470c3b4b1fed12c3baac014be15fac67c6e815 (40位数) 发现有什么不同了没?两种加密之后的密码长度是不是 不一样了 经过MD5加密之后的密码长度为32,而MD5和SHA-1的为40 而我们在 getSaltMd5AndSha (与getSaltMD5代码相同,只是方法名称不一样)中定义的 盐长度为 16位数(即 StringBuilder sBuilder = new StringBuilder(16) ;) 加盐加密后的密码长度为 48位数 (即 char[] cs = new char[48]; ) 加盐加密后的密码长度 = 盐长度 + MD5加密的密码长度 (即 48 = 16 + 32 ) 长度刚刚等于48位数,所以char[] cs 刚好可以把 密码和盐全部都存储起来,可是MD5和SHA-1 加密的密码长度为40,即 48 < 40 + 16,还会有八位数不能够存储到char[] cs 中, 这也就意味着 char[] cs 只能够存储 MD5和SHA-1 加密密码的前32位数 和 16位数的盐,MD5和SHA-1 加密密码的最后八位会丢失,而在验证加盐后是否和原密码一致的getSaltverifyMd5AndSha(String password, String md5str)方法中,我们定义的 char[] cs1 = new char[32];(即去掉盐之后的MD5和SHA-1 加密密码)只有32位数,而MD5和SHA-1 加密密码实际位数有40位数,那么在进行encrypPassword.equals(String.valueOf(cs1) ) 时,就会返回false,即原密码与加密密码验证不一致。 所以要解决这个问题,就是把MD5和SHA-1 加密的密码结果 去掉最后8位数,再进行比较,这样就可以验证原密码是否与加密密码一致了。","categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"加密安全","slug":"Java/加密安全","permalink":"https://blog.hasaik.com/categories/Java/%E5%8A%A0%E5%AF%86%E5%AE%89%E5%85%A8/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"MD5","slug":"MD5","permalink":"https://blog.hasaik.com/tags/MD5/"},{"name":"SHA-1","slug":"SHA-1","permalink":"https://blog.hasaik.com/tags/SHA-1/"}]},{"title":"使用增强版valine","slug":"about-hexo-valine","date":"2019-11-15T11:48:15.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/bf9eba42.html","link":"","permalink":"https://blog.hasaik.com/posts/bf9eba42.html","excerpt":"","text":"云淡风轻在很早之前就开发了一款极简的valine评论系统,由于现在Disqus被墙,使用起来步骤太复杂,所以现在大多数个人博客还是以valine为主。但是原生的valine功能过于单调,后来有不少大佬在基于原作者的基础上增强了valine的功能,我想推荐的一款就是 Deserts 增强的valine,作者介绍文档并没有针对hexo博客做出适配,所以我记录一下我在hexo博客中做出的修改。 相比于原生valine,作者做出了以下增强功能: 支持博主标记显示 必须填写昵称和邮箱才能评论(反垃圾评论的作用) 支持PJAX主题 可以自定义表情包,评论样式修改,点赞功能(新版中被作者去掉了。。。) 评论表情包 支持Disqus数据迁移到valine 隐私保护:敏感字段限制读取,如E-mail、用户信息(使用的浏览器等)、IP 头像显示、样式美化 完善的邮件通知 基于Akimmet的垃圾评论自动标注和过滤 通知邮件补发 …… 评论在线预览,具体介绍可以参考作者原文博客 为了适配hexo博客,可以做如下修改: 先下载作者给出的 Valine.min.js 文件,放到 hexo/themes/next/source/js/src 下,或者你的其它托管路径下 然后修改代码如下: 文件位置:hexo/themes/next/layout/_third-party/comments/valine.swig 12345678910111213141516171819202122232425262728293031{% if theme.valine.enable and theme.valine.appid and theme.valine.appkey %} <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script> <script src="/js/src/Valine.min.js"></script> <!--<script src="//unpkg.com/valine/dist/Valine.min.js"></script>--> <!-- https://deserts.io/diy-a-comment-system/ --> <script type="text/javascript"> new Valine({ lang: 'zh-cn', admin_email: '[email protected]', //博主邮箱 el: '#comments' , appId: '{{ theme.valine.appid }}', appKey: '{{ theme.valine.appkey }}', emoticon_url: 'https://cloud.panjunwen.com/alu', emoticon_list: ["吐.png","喷血.png","狂汗.png","不说话.png","汗.png","坐等.png","献花.png","不高兴.png","中刀.png","害羞.png","皱眉.png","小眼睛.png","中指.png","尴尬.png","瞅你.png","想一想.png","中枪.png","得意.png","肿包.png","扇耳光.png","亲亲.png","惊喜.png","脸红.png","无所谓.png","便便.png","愤怒.png","蜡烛.png","献黄瓜.png","内伤.png","投降.png","观察.png","看不见.png","击掌.png","抠鼻.png","邪恶.png","看热闹.png","口水.png","抽烟.png","锁眉.png","装大款.png","吐舌.png","无奈.png","长草.png","赞一个.png","呲牙.png","无语.png","阴暗.png","不出所料.png","咽气.png","期待.png","高兴.png","吐血倒地.png","哭泣.png","欢呼.png","黑线.png","喜极而泣.png","喷水.png","深思.png","鼓掌.png","暗地观察.png"], placeholder: '{{ theme.valine.placeholder }}', }); <!--点击邮件中的链接跳转至相应评论--> if(window.location.hash){ var checkExist = setInterval(function() { if ($(window.location.hash).length) { $('html, body').animate({scrollTop: $(window.location.hash).offset().top-90}, 1000); clearInterval(checkExist); } }, 100); } </script>{% endif %} 这样就可以基本替代了,但是…但是还是存在不少BUG,比如,原生valine自带的首页元数据评论数量统计不见了,有人提出了issue,作者表示无意开发这个功能。 如果你懂一些css知识的话,评论样式依旧可以自己在 cuntom.styl 文件中修改。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Valine","slug":"Valine","permalink":"https://blog.hasaik.com/tags/Valine/"}]},{"title":"Hexo博客+Next主题归档页美化","slug":"about-hexo-archivesbeautiful","date":"2019-11-14T15:39:14.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/f68f129b.html","link":"","permalink":"https://blog.hasaik.com/posts/f68f129b.html","excerpt":"","text":"大家在使用hexo博客的Next主题的时候应该都觉得原来默认的归档页面很丑吧,最近也有小伙伴问我这个归档页面美化怎么弄的,今天就小小的总结一下。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}我的归档页面https://hasaik.com/archives/ 首先我们打开Next主题目录(注意这个美化样式只支持Next主题),然后找到 next/layout/_macro/post-collapse.swig 文件。 注意:Next5和Next6主题下这个文件中内容可能会有点不太一样,需要找到对应内容对应一一修改。 以下基于Next5,其中原始内容如下,Next6主题下类似: 文件位置:/hexo/next/layout/_macro/post-collapse.swig 12345678910111213141516171819202122232425262728293031323334{% macro render(post) %} <article class="post post-type-{{ post.type | default('normal') }}" itemscope itemtype="http://schema.org/Article"> <header class="post-header"> <{% if theme.seo %}h3{% else %}h2{% endif %} class="post-title"> {% if post.link %}{# Link posts #} <a class="post-title-link post-title-link-external" target="_blank" href="{{ url_for(post.link) }}" itemprop="url"> {{ post.title or post.link }} <i class="fa fa-external-link"></i> </a> {% else %} <a class="post-title-link" href="{{ url_for(post.path) }}" itemprop="url"> {% if post.type === 'picture' %} {{ post.content }} {% else %} <span itemprop="name">{{ post.title | default(__('post.untitled')) }}</span> {% endif %} </a> {% endif %} </{% if theme.seo %}h3{% else %}h2{% endif %}> <div class="post-meta"> <time class="post-time" itemprop="dateCreated" datetime="{{ moment(post.date).format() }}" content="{{ date(post.date, config.date_format) }}" > {{ date(post.date, 'MM-DD') }} </time> </div> </header></article>{% endmacro %} 然后主要找 class 属性做修改,首先将 post-meta 代码块的内容上移到 post-header 下面,如下: 123456789101112131415161718192021222324252627282930313233343536373839404142{% macro render(post) %} <article class="post post-type-{{ post.type | default('normal') }}" itemscope itemtype="http://schema.org/Article"> <header class="post-header"> + <div class="post-meta">+ <time class="post-time" itemprop="dateCreated"+ datetime="{{ moment(post.date).format() }}"+ content="{{ date(post.date, config.date_format) }}" >+ {{ date(post.date, 'MM-DD') }}+ </time>+ </div> <{% if theme.seo %}h3{% else %}h2{% endif %} class="post-title"> {% if post.link %}{# Link posts #} <a class="post-title-link post-title-link-external" target="_blank" href="{{ url_for(post.link) }}" itemprop="url"> {{ post.title or post.link }} <i class="fa fa-external-link"></i> </a> {% else %} <a class="post-title-link" href="{{ url_for(post.path) }}" itemprop="url"> {% if post.type === 'picture' %} {{ post.content }} {% else %} <span itemprop="name">{{ post.title | default(__('post.untitled')) }}</span> {% endif %} </a> {% endif %} </{% if theme.seo %}h3{% else %}h2{% endif %}> - <div class="post-meta">- <time class="post-time" itemprop="dateCreated"- datetime="{{ moment(post.date).format() }}"- content="{{ date(post.date, config.date_format) }}" >- {{ date(post.date, 'MM-DD') }}- </time>- </div> </header></article>{% endmacro %} 然后对照下面代码修改 class 属性,红色代码为原始代码,绿色代码为修改后的代码,实际上修改的地方只是在对应的 class 属性前面加上 my- 即可,比如原始是 post-title-link ,修改为 my-post-title-link 。注意以下只是原始代码和修改代码参考对比,不要直接复制! 1234567891011121314151617181920212223242526272829303132333435363738394041{% macro render(post) %}- <article class="post post-type-{{ post.type | default('normal') }}" itemscope itemtype="http://schema.org/Article">+ <article class="my-post post-type-{{ post.type | default('normal') }}" itemscope itemtype="http://schema.org/Article">- <header class="post-header">+ <header class="my-post-header">- <div class="post-meta">+ <div class="my-post-meta">- <time class="post-time" itemprop="dateCreated"+ <time class="my-post-time" itemprop="dateCreated" datetime="{{ moment(post.date).format() }}" content="{{ date(post.date, config.date_format) }}" > {{ date(post.date, 'MM-DD') }} </time> </div>- <{% if theme.seo %}h3{% else %}h2{% endif %} class="post-title">+ <{% if theme.seo %}h3{% else %}h2{% endif %} class="my-post-title"> {% if post.link %}{# Link posts #}- <a class="post-title-link post-title-link-external" target="_blank" href="{{ url_for(post.link) }}" itemprop="url">+ <a class="my-post-title-link post-title-link-external" target="_blank" href="{{ url_for(post.link) }}" itemprop="url"> {{ post.title or post.link }} <i class="fa fa-external-link"></i> </a> {% else %}- <a class="post-title-link" href="{{ url_for(post.path) }}" itemprop="url">+ <a class="my-post-title-link" href="{{ url_for(post.path) }}" itemprop="url"> {% if post.type === 'picture' %} {{ post.content }} {% else %} <span itemprop="name">{{ post.title | default(__('post.untitled')) }}</span> {% endif %} </a> {% endif %} </{% if theme.seo %}h3{% else %}h2{% endif %}> </header></article>{% endmacro %} 最后打开主题目录下的 next/source/css/_custom/custom.styl 文件,在文件末尾添加如下样式: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123/* 归档页样式 began */.page-archive .archive-page-counter { font-size: 18px; background-color: #49b1f5; padding-left: 10px; padding-right: 10px; border-radius: 8px; color: #fff; +mobile() { font-size: 16px; }}.my-post-time{ font-size: 11px; position: absolute; color: #fff; background-color: #49b1f5; border-radius: 5px; padding-left: 5px; padding-right: 5px; margin-left: 15px;}.mypost{ position: relative; margin-bottom: 1rem; -webkit-transition: all .2s ease-in-out; -moz-transition: all .2s ease-in-out; -o-transition: all .2s ease-in-out; -ms-transition: all .2s ease-in-out; transition: all .2s ease-in-out;}a.my-post-title-link:before{ top: 10px; width: 18px; height: 18px; content: "📚"; margin-right: 5px; font: normal normal normal 14px/1 FontAwesome; font-size: 15px; line-height: 18px;}.my-post:hover{ transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0,0,0,.12), 0 0 6px 0 rgba(104, 104, 105, 0.1); border-radius: 30px; width: 400px; padding: 1px 10px; margin-left: 25px; font-size: 16px; transition-duration: 0.15s; +mobile(){ width: 260px; margin-left: 18px; } //display:flex;}a.my-post-title-link{ text-decoration: none; font-size: 15px; font-weight: 400; +mobile() { font-size: 14px; }}.my-post-title{ display: block; margin-left: 4.5rem; color: #4c4948; text-decoration: none; font-size: .8rem; cursor: pointer; +mobile() { //margin-left: 4rem; }}.my-post-header{ position: top; margin-bottom: 1rem; -webkit-transition: all .2s ease-in-out; -moz-transition: all .2s ease-in-out; -o-transition: all .2s ease-in-out; -ms-transition: all .2s ease-in-out; transition: all .2s ease-in-out;}//.my-post-title-link{// font-size: 16px;// font-weight: 500;//}.my-post-meta{ position: absolute; color: #99a9bf; width: 80px; color: #114142;}div.post-block.tag .collection-title h2 { border-width: 1px; border-style: solid; border-color: #3f3f3f; border-radius: 20px; font-size: 22px; background-color: #b4e8fa; padding: 2px 15px; letter-spacing: 1.5px; box-sizing: border-box; color: #3f3f3f; display: inline-block; margin: 10px 0 10px; text-align: center; +mobile(){ font-size: 18px; }}.category-list-link:hover{ transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0,0,0,.12), 0 0 6px 0 rgba(104, 104, 105, 0.1); border-radius: 8px; padding: 1px 1px; margin-left: 5px; font-size: 16px; transition-duration: 0.15s; //display:flex;}/* 归档页样式 end */ 然后 hexo clean && hexo g && hexo s 就可以查看效果了!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"}]},{"title":"Hexo博客Valine评论样式美化","slug":"about-hexo-valinebeautiful","date":"2019-11-14T15:36:50.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/89ea6c8b.html","link":"","permalink":"https://blog.hasaik.com/posts/89ea6c8b.html","excerpt":"","text":"Valine 是一款比较轻量级的纯前端的评论系统,目前很多个人博客都在使用 Valine 评论系统,并且支持匿名留言。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Valine的Github地址https://github.com/xCss/Valine 我个人也是很喜欢这一款评论系统的,之前用过来必力、Gitalk 等评论系统,都觉得很难用,尤其是来必力,这款韩国人做的评论系统在国内使用很容易出现加载非常慢的情况,往往博客内容都加载完成评论系统还需要好久才加载完,在国内体验比较差吧。 那么 Valine 默认的样式其实比较素,当然不同人喜欢的风格都不一样,如果有喜欢我这种评论样式的,不妨留个言并且在文章末尾给个五星好评吧~ 注意:本Valine美化目前只适应于 valine1.3.4 版本的,如果是其它版本的可能css样式会错乱。 具体更改 Valine 版本的话,主要是更改Valine的js版本,比如我的是next主题,那么就在主题目录下 next\\layout\\_third-party\\comments\\valine.swig 中找到引入 valine.js 的 <script></script> 语句,我是将 Valine 的 1.3.4 版本的 js 放到目录本地的,所以将引入 js 的语句改成了:<script src="/js/src/valine1.3.4.js"></script> 当然不同版本的next主题中 valine.swig 中内容也不同。 比如next6主题的 valine.swig 中代码是这样的: 12345{% set valine_uri = '//unpkg.com/valine/dist/Valine.min.js' %}{% if theme.vendors.valine %} {% set valine_uri = theme.vendors.valine %}{% endif %}<script src="{{ valine_uri }}"></script> 而next5主题的就在valine.swig开头,内容如下: 123{% if theme.valine.enable and theme.valine.appid and theme.valine.appkey %} <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script> <script src="//unpkg.com/valine/dist/Valine.min.js"></script> 而url中 //unpkg.com/valine/dist/Valine.min.js 默认是引入最新的 valine.js 文件,所以不管是next5还是next6主题都是修改这个url地址,只要修改成 valine1.3.4 版本的js文件即可。 然后打开主题目录下 next\\source\\css\\_custom\\custom.styl ,在文件末尾添加如下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217/*valine 评论系统样式*/div#comments.comments.v{ margin-top: 0px !important; margin-left: 0px !important; margin-right: 0px !important;}div.vheader.item2{ border-bottom: 1px solid #5f5f5f; height: 35px !important;}.v .vwrap .vheader.item2 .vinput{ height: 30px !important; border: 0px !important; width: 25% !important; margin: 0px !important;}input.vnick.vinput{ border-right: 2px solid #a4d8fa !important;}div.vcontrol{ padding-top: 0px !important;}div#comments.comments.v{ border: 0px;}.v .vwrap{ border: 2px solid black !important; height: 250px !important; border-radius: 6px !important; overflow: visible !important; counter-reset: avater;}.v .vwrap .vedit .vemojis{ width: 600px !important; background-color: #fff !important; border-radius: 5px !important;}.v .vwrap .vedit .vpreview { width: 600px !important; background-color: #fff !important; border-radius: 5px !important;}.v .vbtn{ background-color: #971212 !important; color: #fff !important;}.v .vwrap .vedit .vctrl{ text-align: left !important;}.v .vwrap .vedit .vctrl span{ background-color: #7f7f7f !important; color: #fff !important; border-radius: 3px !important; padding: 3px !important;}.v .vwrap .vedit .vctrl{ padding: 0px !important; margin: 0px !important;}.v .vlist .vcard .vquote .vcontent { font-size: 15px; font-weight: 200;}div.vedit{ height: 120px;}div.vcontrol{ margin-top: 30px;}.v .veditor{ min-height: 70px !important; height: 100px !important;}.v .vlist .vcard { border: 1px solid #ccc !important; padding-left: 14px !important; padding-right: 14px !important; margin-bottom: 20px !important; border-radius: 10px !important;}.v .vlist .vquote .vcard{ border: 0px !important; margin-bottom: 0px !important; border-radius: 0px !important; padding: 0px !important;}.v .vlist .vcard .vhead .vsys{ display:none !important; background-color: #fff !important;}.v .vlist .vcard .vh .vmeta .vat{ background-color: #177714 !important; color: #fff !important; border-radius: 3px !important; padding-left: 10px !important; padding-right: 10px !important;}.v .vlist .vcard .vimg{ margin: 0 12px 0 0; counter-increment: avater;}/*设置评论头像旋转*/.v .vlist .vcard .vimg:hover { -webkit-transform: rotate(360deg); -moz-transform: rotate(360deg); -ms-transform: rotate(360deg); -transform: rotate(360deg);}.v .vlist .vcard .vquote{ margin-left: 40px; }.v .vlist .vcard .vquote{ counter-reset: avaters;}.v .vlist .vcard .vquote .vimg{ display: avaters !important;}.v .vlist .vcard .vquote .vhead:before{ display: block; float: left; width: 38px; height: 38px; line-height: 38px; margin: 0 12px 0 0; color: #fff; font-size: 15px; font-weight: bold; font-style: normal; background-color: #2d4e41; border: 3px solid #60a1e5; border-radius: 50%; text-align: center; /*content: counter(avater)'.'counter(avaters); counter-increment: avaters;*/}.v .vlist .vcard p { top: -1.5em; position: relative; z-index: 1; margin: unset;}.v .vlist .vcard .vquote a.at { font-size: 13px; color: #bea124; text-decoration: none; border: unset; position: relative; top: -40px;}.v .vlist .vcard .vquote .vcontent{ font-size: 15px; font-weight: 200;}.v .vlist .vcard .vcontent { margin-top: 58px !important; font-size: 15px !important; font-weight: 500 !important; padding-top: 0 !important; margin-bottom: unset !important;}.v .vlist .vcard .vquote .vhead .vnick { color: #5af !important; font-weight: 300 !important; font-size: 15px !important;}.v .vlist .vcard .vhead .vnick { font-size: 18px !important; font-weight: 500 !important; color: #5b6b68 !important;}.v .vlist .vcard{ padding-top: 8px !important;}.v .vlist .vcard .vhead{ float: left !important;}.v .vlist .vcard .vh .vmeta{ float: right !important;}.v .vlist .vcard .vcontent.expand:after{ content: "点击查看全部" !important; font-weight: 400 !important;}/**/ 其中有需要自己修改的地方可以在浏览器中F12自行修改css样式即可。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Valine","slug":"Valine","permalink":"https://blog.hasaik.com/tags/Valine/"}]},{"title":"Hexo博客+Next主题鼠标点击特效","slug":"about-hexo-mouseclick","date":"2019-11-14T15:30:09.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/9c9b482b.html","link":"","permalink":"https://blog.hasaik.com/posts/9c9b482b.html","excerpt":"","text":"感觉鼠标点击出现效果也还不错哈,不妨在本站空白处点击一下看看效果~ 效果 配置 在主题 _config.yml 中添加动态配置项 123cursor_effect: enabled: true type: love # fireworks:礼花 | explosion:爆炸 | love:浮出爱心 | text:浮出文字 代码 文件位置:/themes/next/layout/_custom/custom.swig ,添加如下代码: 12345678910111213{% if theme.cursor_effect %} {% if theme.cursor_effect.type == "fireworks" %} <script src="/js/cursor/fireworks.js"></script> {% elseif theme.cursor_effect.type == "explosion" %} <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> <script src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> <script src="/js/cursor/explosion.min.js"></script> {% elseif theme.cursor_effect.type == "love" %} <script src="/js/cursor/love.min.js"></script> {% elseif theme.cursor_effect.type == "text" %} <script src="/js/cursor/text.js"></script> {% endif %}{% endif %} 如果是第一次使用这个 custom.swig ,则需要在 /themes/next/layout/_layout.swig 中的 </body> 结束之前引入 1{% include '_custom/custom.swig' %} 将以下4个 JS 文件复制到目录 /themes/next/source/js/cursor/ 下 fireworks.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154class Circle { constructor({ origin, speed, color, angle, context }) { this.origin = origin this.position = { ...this.origin } this.color = color this.speed = speed this.angle = angle this.context = context this.renderCount = 0 } draw() { this.context.fillStyle = this.color this.context.beginPath() this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2) this.context.fill() } move() { this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3) this.renderCount++ }}class Boom { constructor ({ origin, context, circleCount = 16, area }) { this.origin = origin this.context = context this.circleCount = circleCount this.area = area this.stop = false this.circles = [] } randomArray(range) { const length = range.length const randomIndex = Math.floor(length * Math.random()) return range[randomIndex] } randomColor() { const range = ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] return '#' + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) } randomRange(start, end) { return (end - start) * Math.random() + start } init() { for(let i = 0; i < this.circleCount; i++) { const circle = new Circle({ context: this.context, origin: this.origin, color: this.randomColor(), angle: this.randomRange(Math.PI - 1, Math.PI + 1), speed: this.randomRange(1, 6) }) this.circles.push(circle) } } move() { this.circles.forEach((circle, index) => { if (circle.position.x > this.area.width || circle.position.y > this.area.height) { return this.circles.splice(index, 1) } circle.move() }) if (this.circles.length == 0) { this.stop = true } } draw() { this.circles.forEach(circle => circle.draw()) }}class CursorSpecialEffects { constructor() { this.computerCanvas = document.createElement('canvas') this.renderCanvas = document.createElement('canvas') this.computerContext = this.computerCanvas.getContext('2d') this.renderContext = this.renderCanvas.getContext('2d') this.globalWidth = window.innerWidth this.globalHeight = window.innerHeight this.booms = [] this.running = false } handleMouseDown(e) { const boom = new Boom({ origin: { x: e.clientX, y: e.clientY }, context: this.computerContext, area: { width: this.globalWidth, height: this.globalHeight } }) boom.init() this.booms.push(boom) this.running || this.run() } handlePageHide() { this.booms = [] this.running = false } init() { const style = this.renderCanvas.style style.position = 'fixed' style.top = style.left = 0 style.zIndex = '999999999999999999999999999999999999999999' style.pointerEvents = 'none' style.width = this.renderCanvas.width = this.computerCanvas.width = this.globalWidth style.height = this.renderCanvas.height = this.computerCanvas.height = this.globalHeight document.body.append(this.renderCanvas) window.addEventListener('mousedown', this.handleMouseDown.bind(this)) window.addEventListener('pagehide', this.handlePageHide.bind(this)) } run() { this.running = true if (this.booms.length == 0) { return this.running = false } requestAnimationFrame(this.run.bind(this)) this.computerContext.clearRect(0, 0, this.globalWidth, this.globalHeight) this.renderContext.clearRect(0, 0, this.globalWidth, this.globalHeight) this.booms.forEach((boom, index) => { if (boom.stop) { return this.booms.splice(index, 1) } boom.move() boom.draw() }) this.renderContext.drawImage(this.computerCanvas, 0, 0, this.globalWidth, this.globalHeight) }}const cursorSpecialEffects = new CursorSpecialEffects()cursorSpecialEffects.init() explosion.min.js 1"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}; love.min.js 1!function(e,t,a){function n(){c(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText="left:"+d[e].x+"px;top:"+d[e].y+"px;opacity:"+d[e].alpha+";transform:scale("+d[e].scale+","+d[e].scale+") rotate(45deg);background:"+d[e].color+";z-index:99999");requestAnimationFrame(r)}function o(){var t="function"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement("div");a.className="heart",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement("style");a.type="text/css";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName("head")[0].appendChild(a)}function s(){return"rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document); text.js 123456789101112131415161718192021222324var a_idx = 0;jQuery(document).ready(function($) { $("body").click(function(e) { var a = new Array("富强", "民主", "文明", "和谐", "自由", "平等", "公正" ,"法治", "爱国", "敬业", "诚信", "友善"); var $i = $("<span/>").text(a[a_idx]); var x = e.pageX, y = e.pageY; $i.css({ "z-index": 99999, "top": y - 28, "left": x - a[a_idx].length * 8, "position": "absolute", "color": "#ff7a45" }); $("body").append($i); $i.animate({ "top": y - 180, "opacity": 0 }, 1500, function() { $i.remove(); }); a_idx = (a_idx + 1) % a.length; });});","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"点击特效","slug":"点击特效","permalink":"https://blog.hasaik.com/tags/%E7%82%B9%E5%87%BB%E7%89%B9%E6%95%88/"}]},{"title":"Hexo博客中加入豆瓣读书页面","slug":"about-hexo-douban","date":"2019-11-14T15:08:54.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/7fbe9500.html","link":"","permalink":"https://blog.hasaik.com/posts/7fbe9500.html","excerpt":"","text":"在Hexo博客个性化定制中,加入豆瓣读书界面是一个很不错的功能,可以进入我的个人阅读界面查看效果,那么我是怎么做到的呢?其实很简单,我们只需要加入一个 hexo-douban 模块即可。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}hexo-douban的Github地址https://github.com/mythsman/hexo-douban 安装模块依赖 我们使用时可以先安装依赖模块,在GitBash中使用以下命令: 1$ npm install hexo-douban --save 站点配置文件中添加配置 然后我们再在Hexo站点根目录配置文件 _config.xml 中的末尾添加如下配置: 12345678910111213douban: user: mythsman builtin: false book: title: 'This is my book title' quote: 'This is my book quote' movie: title: 'This is my movie title' quote: 'This is my movie quote' game: title: 'This is my game title' quote: 'This is my game quote' timeout: 10000 上面参数说明: user: 你的豆瓣ID.打开豆瓣,登入账户,然后在右上角点击 “个人主页” ,这时候地址栏的URL大概是这样:https://www.douban.com/people/xxxxxx/ ,其中的 xxxxxx 就是你的个人ID了。 builtin: 是否将生成页面的功能嵌入hexo s和hexo g中,默认是false,另一可选项为true(1.x.x版本新增配置项)。 title: 该页面的标题. quote: 写在页面开头的一段话,支持html语法. timeout: 爬取数据的超时时间,默认是 10000ms ,如果在使用时发现报了超时的错(ETIMEOUT)可以把这个数据设置的大一点。 由于 hexo-douban 是默认抓取豆瓣读书、豆瓣电影以及豆瓣游戏的,如果只想要其中一部分,可以把其它部分在上述配置文件中去掉即可。 启动 那么我们如何去使用这个呢? 我们只需要在 GitBash 中输入以下命令:hexo clean && hexo douban -bgm && hexo g && hexo s 即可,注意其中开启hexo-douban的命令中,-bgm 代表的是book、game、movie三个参数,如果只需要其中的一部分就只带你想要的那些参数。 另外注意的是,由于 hexo douban 的简写也是 hexo d ,与 hexo deploy 的简写指令 hexo d 冲突,因此在进行二者部署的时候,只能都打全名而不能打简写形式。 测试 上面都没问题之后,我们只需要在站点目录下测试 http://localhost:4000/books 或者 http://localhost:4000/movies 等,如果看到页面了就说明成功了。 部署 如果上述都没有问题,我们就可以在菜单栏中添加按钮了,打开主题配置文件 _config.xml ,找到菜单按钮,可以选择性的添加下面内容: 123456menu: home: / archives: /archives books: /books # 这是链接到books页面 movies: /movies # 这是链接到movies页面 games: /games # 这是链接到games页面 注意添加完成之后按钮并不是中文的,这是由于在 languages 文件夹下面的 zh-CN(中文语言配置文件)没有添加上述对应的中文参数信息,所以我们需要主动添加。 语言文件夹在你的主题配置文件夹下面,比如我的是使用的next主题,则是在 E:\\blog\\hexo\\themes\\next\\languages 目录下,找到 zh-CN 文件,在menu菜单下添加: 1234menu: books: 阅读 movies: 电影 games: 游戏 即可完成中文化自定义菜单。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"豆瓣","slug":"豆瓣","permalink":"https://blog.hasaik.com/tags/%E8%B1%86%E7%93%A3/"}]},{"title":"Hexo博客界面美化","slug":"about-hexo-beautiful","date":"2019-11-14T10:43:11.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/dff7e11c.html","link":"","permalink":"https://blog.hasaik.com/posts/dff7e11c.html","excerpt":"","text":"注意:本样式美化方式只适用于 Next 主题,并且最好懂一些 CSS 前端知识,以便有些不兼容样式部分可以自行在浏览器中 F12 调试。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Next5主题的Github地址https://github.com/iissnan/hexo-theme-next 我的个人博客样式都是基于 Next5 主题的,如果你用的是 Next6 主题,那么会有一部分样式不是很兼容, 需要自己在浏览器中定位该样式并做一些调整。 修改的话就找到next主题目录下的 next\\source\\css\\_custom\\custom.styl ,这个文件是Next主题预留给用户自定义修改css样式的文件,所以我们绝大多数全局样式都在这里进行修改即可。 以下附上我的 custom.styl 文件内容供大家参考,注意出现问题一定要在浏览器中调试修改! 文件位置:hexo/themes/next/source/css/_custom/custom.styl 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499// Custom styles.// 自定义的侧栏时间样式#days { display: block; color: rgb(7, 179, 155); font-size: 13px; //margin-top: 15px; //margin-left: 35px; //margin-bottom: 15px;}/*菜单*/.menu-item:hover { transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(104, 104, 105, 0.1); //border-radius: 3px;}/*近期文章*/.my-links-of-blogroll-li:hover { transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(104, 104, 105, 0.1);}/* 排行榜 */.my-article-top:hover { transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(104, 104, 105, 0.1); border-radius: 30px; width: 580px; padding: 5px 10px; transition-duration: 0.15s; +mobile() { width: 315px; //display: block;//不换行 //margin-left: 18px; } //display:flex;}/*归档页样式优化 began*/.page-archive .archive-page-counter { font-size: 18px; background-color: #49b1f5; padding-left: 10px; padding-right: 10px; border-radius: 8px; color: #fff; +mobile() { font-size: 16px; }}.my-post-time { font-size: 11px; position: absolute; color: #fff; background-color: #49b1f5; border-radius: 5px; padding-left: 5px; padding-right: 5px; margin-left: 15px;}.mypost { position: relative; margin-bottom: 1rem; -webkit-transition: all .2s ease-in-out; -moz-transition: all .2s ease-in-out; -o-transition: all .2s ease-in-out; -ms-transition: all .2s ease-in-out; transition: all .2s ease-in-out;}a.my-post-title-link:before { top: 10px; width: 18px; height: 18px; content: "📚"; margin-right: 5px; font: normal normal normal 14px / 1 FontAwesome; font-size: 15px; line-height: 18px;}.my-post:hover { transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(104, 104, 105, 0.1); border-radius: 30px; width: 550px; padding: 1px 10px; margin-left: 25px; font-size: 16px; transition-duration: 0.15s; +mobile() { width: 260px; margin-left: 18px; } //display:flex;}a.my-post-title-link { text-decoration: none; font-size: 15px; font-weight: 400; +mobile() { font-size: 14px; }}.my-post-title { display: block; margin-left: 4.5rem; color: #4c4948; text-decoration: none; font-size: .8rem; cursor: pointer; +mobile() { //margin-left: 4rem; }}.my-post-header { position: top; margin-bottom: 1rem; -webkit-transition: all .2s ease-in-out; -moz-transition: all .2s ease-in-out; -o-transition: all .2s ease-in-out; -ms-transition: all .2s ease-in-out; transition: all .2s ease-in-out;}//.my-post-title-link{// font-size: 16px;// font-weight: 500;//}.my-post-meta { position: absolute; color: #99a9bf; width: 80px; color: #114142;}div.post-block.tag .collection-title h2 { border-width: 1px; border-style: solid; border-color: #3f3f3f; border-radius: 20px; font-size: 22px; background-color: #b4e8fa; padding: 2px 15px; letter-spacing: 1.5px; box-sizing: border-box; color: #3f3f3f; display: inline-block; margin: 10px 0 10px; text-align: center; +mobile() { font-size: 18px; }}.category-list-link:hover { transform: scale(1.1); box-shadow: 10px 10px 15px 2px rgba(0, 0, 0, .12), 0 0 6px 0 rgba(104, 104, 105, 0.1); border-radius: 8px; padding: 1px 1px; margin-left: 5px; font-size: 16px; transition-duration: 0.15s; //display:flex;}/*归档页样式优化 end */.main { padding-bottom: 150px;}//hexo next主题下,自动更换背景图片 began// 图片来源https://source.unsplash.com/body { //background: url(https://source.unsplash.com/random/1920x1080); background-image: url(/images/bg1.jpeg); background-repeat: no-repeat; background-attachment: fixed; background-position: 50% 50%;}//hexo next主题下,自动更换背景图片 end/*评论数*/.posts-expand .post-comments-count { display: none;}/*鼠标样式*/* { cursor: url(/images/default.cur), auto;}:active { //cursor: url(/images/pointer.cur),auto}:link { cursor: url(/images/pointer.cur), auto}/*文章底部评分相关*/.post-widgets { //padding-top: 9px; margin-bottom: 45px; margin-top: 30px;}/*文章底部标签样式*/.posts-expand .post-tags a { -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24); -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24); box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24); font-family: 'Comic Sans MS', sans-serif; transition: .2s ease-out; padding: 3px 5px; margin: 5px; background: #f5f5f5; border-bottom: none; border-radius: 15px; +mobile() { padding: 1px 3px; font-size: 8px; } &:hover { background: rgba(100, 154, 182, 0.902); color: #fff; -webkit-box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -moz-box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); }}// pc主页文章添加阴影效果.post { margin-top: 0px; margin-bottom: 50px; padding: 25px; -webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5); -moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5);}/* 1~4级标题设置 */h1,h2,h3,h4,strong { font-weight: 600; color: #2c3e50;}blockquote { // PC端>符号表示的内容样式 font-size: 15px; //background: #e8f7ef; //border-left-color: #42b983; background: #d9efdc5e; border-left-color: #0fc530 85;}.tip, blockquote { padding: 12px 24px 12px 30px; margin: 1em 0;}p { margin: 10px 0 10px 0;}/*文章页*/.posts-expand { margin: 0 1px; padding-top: 0px;}.posts-expand .post-body p { font-size: 16px; letter-spacing: 1px; margin: 3px 0; padding-bottom: 4px;}/* 一到六级标题设置 */.posts-expand .post-body h1,.posts-expand .post-body h2,.posts-expand .post-body h3,.posts-expand .post-body h4,.posts-expand .post-body h5,.posts-expand .post-body h6 { padding-top: 10px; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px;}/*修改h1前面图标*/.posts-expand .post-body h1:before { top: 10px; width: 18px; height: 18px; content: "⛅"; font-size: 18px; line-height: 18px; margin-right: 16px;}/*修改h2前面图标*/.posts-expand .post-body h2:before { top: 10px; width: 18px; height: 18px; content: "🌞"; font-size: 18px; line-height: 18px; margin-right: 16px}/*修改h3前面图标*/.posts-expand .post-body h3:before { top: 10px; width: 18px; height: 18px; content: "🔍"; font-size: 18px; line-height: 18px; margin-right: 16px;}.posts-expand .post-body ul li { margin-bottom: 10px; //list-style: none; list-style: disc; color: #000; font-size: 15px; //list-style: disc; //margin-left: 3%;}.posts-expand .post-body ul li p { //margin-bottom: 10px; //border-radius: 6px; text-align: left;}/* 有序图标设置 */ol { padding-left: 0; font-size: 15px; margin-top: .4rem; //padding: 0 0 0 .8rem; list-style: none; counter-reset: ol-li;}.posts-expand .post-body ol li:before { display: block; float: left; width: 17px; height: 17px; line-height: 16px; margin: .4rem 12px 0 0; color: #fff; font-size: 15px; font-weight: 500; font-style: normal; background-color: #49b1f5; border-radius: 50%; text-align: center; content: counter(ol-li); counter-increment: ol-li;}//.posts-expand .post-body ul li {// margin-bottom: 10px;// list-style: none;// color: #000;//}////.posts-expand .post-body ul li:before {// display: block;// float: left;// width: 12px;// height: 12px;// line-height: 28px;// margin: .5rem 12px 0 0;// color: #fff;// font-size: 15px;// font-weight: 700;// font-style: normal;// background-color: #49b1f5;// border-radius: 50%;// text-align: center;// content: "";//}//自定义回到顶部样式.back-to-top { //right: 60px; width: 70px; //图片素材宽度 height: 900px; //图片素材高度 top: -900px; bottom: unset; transition: all .5s ease-in-out; background: url("/images/scroll.png"); //隐藏箭头图标 > i { display: none; } &.back-to-top-on { bottom: unset; top: 100vh < (900px + 200px) ? calc(100vh - 900px - 200px):0px; }}//代码块复制按钮.highlight { //方便copy代码按钮(btn-copy)的定位 //position: relative; position: static;}highlight-wrap { background: #008b89;}.btn-copy { display: inline-block; cursor: pointer; background-color: #eee; background-image: linear-gradient(#fcfcfc, #eee); border: 1px solid #d5d5d5; border-radius: 3px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-appearance: none; font-size: 13px; font-weight: 700; line-height: 20px; color: #333; -webkit-transition: opacity .3s ease-in-out; -o-transition: opacity .3s ease-in-out; transition: opacity .3s ease-in-out; padding: 2px 6px; position: absolute; right: 5px; top: 5px; opacity: 0;}.btn-copy span { margin-left: 5px;}.highlight:hover .btn-copy { opacity: 1;}//pc端.read-over { //本文阅读结束 text-align: center; margin-top: 20px; color: #ea103d91; font-size: 18px;}.share_reward { //pc打赏 margin: 10px auto; width: 90%; text-align: center;}#rewardButton { //打赏 margin: 15px auto;}// 代码块样式 Custom styles.//code {// color: #ff7600;// background: #fbf7f8;// margin: 2px;//}//行内代码样式code { color: #c7254e; background: #f9f2f4; border: 1px solid #d6d6d6; padding: 1px 4px; word-break: break-all; border-radius: 4px;}// 大代码块的自定义样式.highlight, pre { margin: 0px 0; //padding: 5px; border-radius: 0px;}.highlight, pre { border: 1px solid #21252b;}.post-body { color: #000;}.post-body .note { //提示条 font-size: 15px;}/*文章标题字体*/.posts-expand .post-title { font-size: 26px; letter-spacing: 1px; font-weight: 700; text-align: center; +mobile() { font-size: 20px; //margin: 10px; }}/*文章标题动态效果*/.posts-expand .post-title-link::before { background-image: linear-gradient(90deg, #a166ab 0%, #ef4e7b 25%, #f37055 50%, #ef4e7b 75%, #a166ab 100%);}/*文章大标题*/.posts-expand .post-title-link:hover { transform: scale(1.1);}.posts-expand .post-meta { //margin: 3px 0 20px 0; margin-bottom: 20px !important;}// 文章内链接文本样式.post-body a { color: #0593d3; border-bottom: none; border-bottom: 1px solid #0593d3; //text-decoration: underline // none || underline || blink || overline || line-through &:hover { color: #fc6423; //border-bottom: none; //background: white; text-decoration: none; border-bottom: 1px solid #fc6423; }}// 精品文章 began.jingping { background: #00a8c3; padding: 2px 4px 2px 4px; color: #fff;}// 精品文章 end// new ------//热评文章.ds-top-threads li a { padding-left: 5px; transition: border-width 0.2s linear 0s, color 0.2s linear 0s; border-bottom: none;}.ds-top-threads li a:hover { border-left: 8px solid #4d768c;}/*相关文章推荐 pc样式设置*/summary { outline: 0; cursor: pointer; margin-top: 15px; +mobile() { /*手机端*/ font-size: 14px; margin-top: 10px; }}details { margin-left: 20px;}details .popular-posts { +mobile() { margin: 5px -12px; }}.popular-posts-header { margin-top: 45px; font-size: 20px; font-weight: 900; border-bottom: 1px solid #eee; +mobile() { /*手机端*/ font-size: 18px; margin-top: 25px; }}ul.popular-posts .popular-posts-item .popular-posts-title a { border-bottom: 1px solid #999; &:hover { border-bottom: none; }}//小胡同背景图.xiaohutong-img-class { width: 960px; height: 660px;}//友链页样式 begain -------->#links { margin-top: 5rem;}.links-content { margin-top: 1rem;}.link-navigation::after { content: " "; display: block; clear: both;}.card { width: 300px; font-size: 1rem; padding: 10px 20px; border-radius: 4px; transition-duration: 0.15s; margin-bottom: 1rem; display: flex;}.card:nth-child(odd) { float: left;}.card:nth-child(even) { float: right;}.card:hover { transform: scale(1.1); box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04); background-image: linear-gradient(to right, #6a11cb 0, #2575fc 100%);}.card a { border: none;}.card .ava { width: 3rem !important; height: 3rem !important; margin: 0 !important; margin-right: 1em !important; border-radius: 4px;}.card .card-header { font-style: italic; overflow: hidden; width: 236px;}.card .card-header a { font-style: normal; color: #2bbc8a; font-weight: bold; text-decoration: none;}.card .card-header a:hover { color: #d480aa; text-decoration: none;}.card .card-header .info { font-style: normal; color: #a3a3a3; font-size: 14px; min-width: 0; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;}span.focus-links { font-style: normal; margin-left: 10px; position: unset; left: 0; padding: 0 7px 0 5px; font-size: 11px; border-color: #42c02e; border-radius: 40px; line-height: 24px; height: 22px; color: #fff !important; background-color: #42c02e; display: inline-block;}span.focus-links:hover { background-color: #318024;}.friends-btn { text-align: center; color: #555 !important; background-color: #fff; border-radius: 3px; font-size: 15px; box-shadow: inset 0 0 10px 0 rgba(0, 0, 0, .35); border: none !important; transition-property: unset; padding: 0 15px; margin: inherit;}.friends-btn:hover { color: rgb(255, 255, 255) !important; border-radius: 3px; font-size: 15px; box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.35); background-image: linear-gradient(90deg, #a166ab 0%, #ef4e7b 25%, #f37055 50%, #ef4e7b 75%, #a166ab 100%); margin: inherit;}//友链页样式 end -------->//首页头部样式.header { //background: url("/images/header-bk.jpg");}.site-meta { float: none;}.menu { float: none;}.logo-line-before,.logo-line-after { display: none;}.menu .menu-item a { font-size: 14px; color: rgb(15, 46, 65); border-radius: 4px;}.site-meta { margin-left: 0px; text-align: center; //background: #f7edd9 //background-image: linear-gradient(90deg,#f79533 0,#f37055 15%,#ef4e7b 30%,#a166ab 44%,#5073b8 58%,#1098ad 72%,#07b39b 86%,#6dba82 100%)!important; background-image: url(/images/bg.jpeg)}.site-meta .site-title { font-weight: 900; font-size: 28px; font-family: 'Comic Sans MS', sans-serif; color: #fff;}.title1 { color: rgb(66, 133, 244)}.title2 { color: rgb(234, 67, 53)}.title3 { color: rgb(251, 188, 5)}.title4 { color: rgb(66, 133, 244)}.title5 { color: rgb(52, 168, 83)}.title6 { color: rgb(234, 67, 53)}.title7 { color: rgb(66, 133, 244)}.title8 { color: rgb(234, 67, 53)}.site-subtitle { color: #213951; font-weight: bold}#myheartbeat { animation: heartAnimate 1.33s ease-in-out infinite; color: #f50404;}//首页尾部样式.footer { //background: none; //font-size: 14px;}.footer-inner { font-family: 'Comic Sans MS', sans-serif; text-align: center; color: #4c618f;}//侧边栏信息样式修改.site-author-name { margin: 18px 0 0; color: #090909; font-family: 'Comic Sans MS', sans-serif;}.links-of-blogroll { font-size: 13px; margin-bottom: 22px;}.links-of-author { margin-top: 10px; //margin-bottom: 58px;}.site-overview { //左侧socia标签居中 text-align: center;}.sidebar-inner { color: #649ab6;}.sidebar { margin-left: auto; /* for IE */ margin-left: inherit; box-shadow: inset 2px 2px 40px #bdb2b2;}.sidebar a { color: #649ab6; border-bottom-color: #649ab6; border-bottom: none;}.sidebar a:hover { color: #0c0b0b;}.site-state-item { display: inline-block; padding: 8px 18px; border-left: 1px solid #649ab6;}.sidebar-nav .sidebar-nav-active { color: #649ab6; border-bottom-color: #649ab6;}.sidebar-nav li:hover { color: #0c0b0b;}//侧栏描述样式.site-description motion-element {}//侧栏按钮样式.sidebar-toggle { background: #649ab6;}//文章目录样式.post-toc .nav .active > a { color: #4f7e96;}.post-toc ol a:hover { color: #7784ba;}.sidebar-nav .sidebar-nav-active:hover { color: #37596c;}a { border-bottom: none;}//首页阅读全文样式.post-button .btn { color: #555; background-color: rgb(255, 255, 255); border-radius: 3px; font-size: 15px; box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.35); border: none !important; transition-property: unset; padding: 0px 15px; margin: inherit;}.post-button .btn:hover { color: rgb(255, 255, 255); border-radius: 3px; font-size: 15px; box-shadow: inset 0px 0px 10px 0px rgba(0, 0, 0, 0.35); background-image: linear-gradient(90deg, #a166ab 0%, #ef4e7b 25%, #f37055 50%, #ef4e7b 75%, #a166ab 100%); margin: inherit;}//.post-button a{// border-bottom: 1px solid #666;//}//.post-button a:hover {// color: #7784ba;//}// 自定义页脚跳动的心样式 began@keyframes heartAnimate { 0%, 100% { transform: scale(1); } 10%, 30% { transform: scale(0.9); } 20%, 40%, 60%, 80% { transform: scale(1.1); } 50%, 70% { transform: scale(1.1); }}.with-love { animation: heartAnimate 1.33s ease-in-out infinite; color: rgb(255, 113, 168);}// 自定义页脚跳动的心样式 end/*修改选择字体块背景颜色*/::selection { background: #fff159; color: #222;}.pagination { margin: 50px; text-align: center; border-top: 0px;}/* 页码数字显示当前页码样式设置 */.pagination .page-number.current { background-color: #49b1f5; background: #49b1f5; border-radius: 50%;}/* 页码样式设置 */.pagination .page-number { border: 3px solid #49b1f5; border-radius: 50%;}/*valine 评论系统样式 began ------------------> *//*valine 评论系统样式 end ------------------> */// 适配手机样式 began ------------------------>@media (max-width: 767px) { /*手机端body体显示*/ body { background-image: none; font-size: 16px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #34495e; } /*手机端菜单栏样式*/ .menu { display: flex; margin-left: 5px; float: none !important; } //.menu-item{ // display: inline; //} ////不显示站点地图li //.menu .menu-item menu-item-sitemap{ // display:none //} .menu .menu-item a { /*font-weight: 600;*/ color: #2c3e50; font-size: 14px; //padding: 1px 7px; padding: 5px 12px; } //选中菜单样式 .menu-item-active a:after { top: -10%; //top: 66%; right: 24px; //三角形 //width:0; height:0; border:10px solid; border-color:#aececb #f5f7f9 #f5f7f9 #f5f7f9; } blockquote { // 手机端>符号表示的内容样式 //padding: 4px 10px; //margin: 10px auto; //border-left: 3px solid #ddd; } blockquote p { // 手机端>符号表示的内容样式 font-size: 13px; } .back-to-top { display: none; //不显示 right: 0px; opacity: .2; } /*手机屏幕下多级标签样式修改*/ .posts-expand .post-body h1 { margin: 0 0 0em; } .posts-expand .post-body h2, .posts-expand .post-body h3 { pointer-events: none; } .posts-expand .post-body h1, .posts-expand .post-body h2, .posts-expand .post-body h3, .posts-expand .post-body h4 { pointer-events: auto; color: #2c3e50; } .posts-expand .post-body h1:before .posts-expand .post-body h2:before .posts-expand .post-body h3:before .posts-expand .post-body h4:before .posts-expand .post-body h5:before .posts-expand .post-body h6:before { display: none; } .posts-expand .post-body h2 { padding-bottom: 0.7em; border-bottom: 1px solid #ddd; } .posts-expand .post-body h3 { line-height: 1.2; position: relative; } .posts-expand .post-body h3 > a:before { content: ""; color: #42b983; position: absolute; left: -0.7em; margin-top: -0.05em; padding-right: 0.5em; font-size: 1.2em; line-height: 1; font-weight: bold; } .posts-expand .post-body figure { //margin: 1.2em 0; } /*手机段落样式修改*/ .posts-expand .post-body p { line-height: 1.6em; text-align: left; font-size: 14px; //display: contents;//文字左对齐 margin: 5px 0; padding-bottom: 6px; position: relative; z-index: 1; color: #5e6d82; } /* 手机端 列表样式修改*/ .posts-expand .post-body ul, .posts-expand .post-body ol { position: inherit; } .posts-expand .post-body ul ul, .posts-expand .post-body ol ul, .posts-expand .post-body ul ol, .posts-expand .post-body ol ol { margin: 0; } .posts-expand .post-body ul li { font-size: 13px; //margin-bottom: 0px !important; } .posts-expand .post-body a { //color: #000; font-weight: 400; } p { line-height: 1.6em; margin: 1.2em 0 -1.2em; padding-bottom: 1.2em; position: relative; z-index: 1; font-size: 14px; word-spacing: 0.05em; } /*手机端对ul li展示优化*/ .posts-expand .post-body ul li:before { margin: .4rem 12px 0 0; } li { font-size: 14px; color: #5e6d82; } .post-button a { font-size: 16px; } .posts-expand .post-meta { font-size: 13px; text-align: center; margin: 1px 0 20px 0; } /*设置不展示字数统计*/ .posts-expand .post-meta .post-wordcount { //display: none; } /*手机端评论数*/ //.posts-expand .post-comments-count { // +mobile() { display: unset; } //} .page-post-detail .post-meta { margin: 10px 0px; } .page-post-detail .post-title { font-weight: 600; font-size: 20px !important; //padding-top: 10px; //padding-bottom: 15px; } .my_post_copyright { //版权所有 width: 95%; //padding: .1em 1em; font-size: .73rem margin: 2em auto 0 } .my_post_copyright p { font-size: 13px; margin: 5px; line-height: 1.2em; padding-bottom: 0.5em; }}/*适应手机屏幕设置*/@media (max-width: 767px) { .header-inner { margin-bottom: 10px !important; background: none; //overflow: auto; //下拉标题是否浮动 } .posts-expand .post-body h1 { padding-top: 20px; } .post-button { text-align: center; } .posts-expand .post-body h2, .posts-expand .post-body h3 { pointer-events: none; } .posts-expand .post-body h1, .posts-expand .post-body h2, .posts-expand .post-body h3, .posts-expand .post-body h4 { pointer-events: auto; color: #2c3e50; } .posts-expand .post-body h1:before .posts-expand .post-body h2:before .posts-expand .post-body h3:before .posts-expand .post-body h4:before .posts-expand .post-body h5:before .posts-expand .post-body h6:before { display: none; } .posts-expand .post-body h2 { padding-bottom: 0.7em; border-bottom: 1px solid #ddd; } .posts-expand .post-body h3 { line-height: 1.2; position: relative; }}/*手机端显示设置信息*/@media (max-width: 767px) { /*手机端代码行上text文档设置*/ .highlight figcaption { font-size: 12px; } /*手机端显示代码行数背景设置*/ .highlight .gutter pre { padding-left: 2px; } /*手机端显示代码行数字体大小*/ .highlight, pre { font-size: 13px; } /*手机端显示代码字体大小*/ .code span { font-size: 12px; } div#comments.comments.v { margin-left: 0px !important; margin-right: 0px !important; } .comments { margin: 20px 10px 0; }}//手机适配文章底部信息@media (max-width: 767px) { .main { padding-bottom: 130px; } .footer { font-size: 12px; } .read-over { //本文阅读结束 text-align: center; margin-top: 20px; color: #ea103d91; font-size: 12px; } .share_reward { //打赏 font-size: 12px; padding: 10px 0; margin: 5px auto; width: 90%; text-align: center; } #rewardButton { margin: 15px auto; } #rewardButton span { //打赏 display: inline-block; width: 50px; height: 35px; } #QR { padding-top: 5px; } #QR a { border: 0; } #QR img { width: 60px; //max-width: 50%; display: inline-block; margin: 0.8em 2em 0 2em; } .post-copyright { //外版权 padding: .1em .5em; margin: 0em 0 0; border-left: 2px solid #ff1700; } .post-copyright li { //外版权 font-size: 13.5px; } .posts-expand .post-tags { margin-top: 10px; font-size: 8px; } .post-nav-item a { font-size: 12px; line-height: unset; } .pagination { // 分页按钮 margin: 20px; text-align: center; border-top: 0px; } /*TopX适应手机屏幕设置*/ #top p { font-size: 12px; display: inline-block; //不换行 } .post-body .note { //提示条 margin: 10px auto; font-size: 14px; } .post-body .note.info p { font-size: 13px; } .post-body .tabs .tab-content .note { margin: 10px auto; font-size: 14px; }}@media (max-width: 767px) { //小胡同背景图 .xiaohutong-img-class { width: 360px; height: 260px; } // 小胡同里tab页 .post-body .tabs ul.nav-tabs { display: flex; } /* 手机端文章布局 */ .post { margin-bottom: 20px; padding: 10px; //-webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5); //-moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5); } //手机友链页 .card { width: 88%; padding: 3px 20px; margin-bottom: 0rem; } #links { margin-top: 3rem; } /*移动端 本地搜索框美化*/ .local-search-popup { top: 10%; margin: 10px 35px; width: 80%; height: 60%; } .local-search-popup .search-icon, .local-search-popup .popup-btn-close { color: #15a1d8 f2; } .local-search-popup .local-search-input-wrapper input { padding-left: 10px; height: 21px; background-color: rgb(255, 255, 255); } .local-search-popup .popup-btn-close { border-left: none; } .local-search-popup p.search-result { padding-bottom: 1.2em; font-size: 13px; margin: .1em 0 .5em; } .local-search-popup a.search-result-title { font-size: 14px; }}// 适配手机样式 end ------------------------>","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"}]},{"title":"Hexo博客+Next主题深度优化与定制","slug":"about-hexo","date":"2019-10-17T16:17:56.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/ab21860c.html","link":"","permalink":"https://blog.hasaik.com/posts/ab21860c.html","excerpt":"","text":"写在前面 warning,本教程只适用于 Next5 或者 Next6 主题,Next7 开始做了大量修改,并删除了 custom.styl 文件,同时增加了很多在 Next7 之前需要手动配置的功能,请随个人喜好进行版本选择。 info,我的个人博客就是使用 Hexo 博客框架 + Next 主题搭建而来的,之前也使用过CSDN、博客园等,最后都放弃了,一方面是因为广告多,另外一方面样式我也不是很喜欢,而如果自己从零开始写博客源代码的话,比较复杂而且麻烦。后来偶然看到了 hexo 博客框架,并经过推荐使用了 Next 主题,这才正式入了 hexo 博客的坑!不得不说 Next 主题能够魔改并且自身集成了很多优秀的第三方插件是这款主题具有如此强大活力的根本原因😘。 本文下面主要先介绍 Hexo 博客和 Next 主题的搭建,如果你已经搭建好了博客框架,但是想进一步地修改博客样式,可以直接跳转到最下面优化定制部分😄,本文参考的博客链接也会直接在文中插入或者在文末标明,如果有遗漏,欢迎指出。 Next 主题最新版本已经更新到 v7.5.0,以下很多内容都已经在新主题中做出了适配或者直接无法使用,愿意更新到最新版本的小伙伴请关注官方文档更新。不愿意更新的小伙伴或者目前使用的是旧版本(比如我还是 Next5 版本)可以继续进行一定的参考。 环境准备 在安装 hexo 框架之前,我们需要先安装该框架的依赖环境: Node.js Git 因为 Hexo 博客框架就是基于 Node.js 渲染的,所以必须要先安装 Node.js 环境,我们可以去Node.js中文官网下载,如图 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}安装地址http://nodejs.cn/download/ 一般我们是在 windows 或者 macOS 环境下作为本机操作的,所以下载对应的安装包就可以了,下载好后一路点 next 下一步就完成了,这个没什么问题。 然后我们需要安装一下 Git,Git 主要是帮助我们部署到 Github Pages 静态仓库上以域名形式访问。 安装 Git 的话,如果是 windows 系统,可以直接去Windows的Git下载地址去下载,如果是 macOS 的话,也可以在这里下载。当这些环境都部署好之后,就可以开始我们的 hexo 博客安装啦! .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Windows的Git下载地址https://gitforwindows.org/ .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}MacOS的Git下载地址https://www.git-scm.com/download/ 安装 Hexo 和 Next 安装 hexo 本文安装环境为 windows10,所以以下都以 windows 操作系统下安装为例。 danger,安装之前需要说明几个注意事项:- 很多命令既可以使用 windows 的 cmd 命令行来完成,也可以使用刚才安装好的 Git 命令行工具 Git Bash 来完成,但是在 cmd 中部分命令会出现一些问题,建议只使用 Git Bash 来执行命令。- Hexo 不同版本之间有差别,要注意自己安装的版本是哪个版本(跟着本文走就没问题啦),如果修改样式的话注意网上教程的 Hexo 版本差异。- Hexo 安装好后有 2 种_config.xml文件,一个是 hexo 站点根目录下的全局_config.xml文件,还有一种是每个主题 theme 下的各自的_config.xml文件,注意区分二者,后面会详细说到。 安装 hexo 依然是在 GitBash 中操作,输入以下命令,等待安装完成。 1$ npm install -g hexo-cli 创建 hexo 文件夹 在电脑某个位置创建一个名为 hexo 的文件夹(当然名字可以随便取),比如我个人的就是 E:\\blog\\hexo ,由于这个文件夹是你以后存放博客代码和文章的地方,所以最好不要乱放,然后我们在 GitBash 中使用 cd 命令移动到创建好的文件夹中。 1$ cd /e/blog/hexo 初始化 hexo 在上面 cd 到创建的文件夹后,输入以下命令进行初始化 1$ hexo init 这个命令执行的时间非常长,主要是初始化 hexo 博客中的文件夹,包括 hexo 博客内置的各种 node_modules 组件等等,所以耐心的稍等一下!如果初始化失败建议删除文件夹内容后重试。 初始化完成后,我们打开刚才创建的文件夹,会发现里面包含如下文件夹: 注意,其中有一个_config.xml文件,这个我们叫做站点根目录配置文件,里面的初始内容如下:(附上中文介绍) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# Hexo Configuration## Docs: https://hexo.io/docs/configuration.html## Source: https://github.com/hexojs/hexo/# Site 站点主配置title: Hexo # 网站标题subtitle: # 网站副标题description: # 网站描述keywords: # 可以不填写保持默认author: John Doe # 网站拥有者昵称language: # 网站语言设置,一般根据依赖的主题而定timezone: # 网站时区设置,一般不填写保持默认# URL地址链接设置url: http://yoursite.com # 网站url设置root: / # 网站根目录链接permalink: :year/:month/:day/:title/ # 文章链接,默认是按照 /年/月/日/文章标题 设置的链接permalink_defaults: # 默认链接形式# Directory 网站主要目录,这里一般不做改动source_dir: sourcepublic_dir: publictag_dir: tagsarchive_dir: archivescategory_dir: categoriescode_dir: downloads/codei18n_dir: :langskip_render:# Writing 网站文章设置,同样一般不做改动new_post_name: :title.md # File name of new postsdefault_layout: posttitlecase: false # Transform title into titlecaseexternal_link: true # Open external links in new tabfilename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: falsefuture: truehighlight: enable: true line_number: true auto_detect: false tab_replace: # Home page setting 主页设置,一般不做改动index_generator: path: '' per_page: 10 order_by: -date # 首页文章排序,默认是按照文章日期递减 # Category & Tag 分类设置,一般不做改动default_category: uncategorizedcategory_map:tag_map:# Date / Time format 日期设置,一般不做改动date_format: YYYY-MM-DDtime_format: HH:mm:ss# Pagination 导航页设置,一般不做改动per_page: 10 # 设置每页展示多少文章pagination_dir: page# Extensions 使用的主题名称,可以在这里切换theme: next # 此处切换主题名称# Deployment 部署,一般选择部署到Github上deploy: type: 其实到这里来说,我们的 hexo 博客已经做好了!不信?我们执行下面命令看看: 123//cd到根目录执行$ hexo g$ hexo s 然后我们打开浏览器,输入 http://localhost:4000 ,是不是惊奇的发现已经完成了? 等….等一下,页面怎么是英文的😫???! 别慌,上面提到配置文件的时候已经说到是语言没有修改了,我们可以打开 hexo 目录下的 themes 文件夹,发现里面有一个 landscape 文件夹,没错,你刚才看到的默认主题就是这个名叫 landscape 的主题,这个主题是 hexo 博客自带的默认主题,当然我们可以下载其它主题来代替它,具体方式在后面会介绍。 然后我们先来分析一下这个 landscape 主题文件夹: 注意这里面有一个 _config.xml 配置文件,其实上面已经强调过,这个跟前面的站点根目录下配置文件同名,但是这个配置文件是在主题目录下的,那么这个配置文件我们一般就叫做主题配置文件,基本每个 hexo 博客第三方主题下面都会有这个配置文件,所以主题配置文件是一种统称。 里面的初始内容我们暂时不管(因为不同主题的配置文件内容不一样,反正我们又不使用这个主题~)。 先来看看上面的 languages 文件夹,一看就懂,都知道这就是网站语言配置文件,好,我们进去看一下,噢~里面的 zh-CN 和 zh-TW 可不就是中文吗。其中 zh-CN 指的是简体中文,zh-TW(湾湾)是繁体中文,欧克。然后我们再去站点根目录下,注意这里是 站点根目录下(hexo/_config.xml)的配置文件中(千万别懵逼了),将这里: 12345678# Site 站点主配置title: sanarous教你搭建Hexo博客 //网站标题修改subtitle: so easy~ //副标题修改description: //网站描述keywords: hexo,next,Java,博客 //网站关键字,用英文逗号分开author: Sanarous //此处填写你自己的昵称 + language: zh-CN //这里改成上面在主题配置文件中看到的语言名字,注意英文冒号:后面有一个空格timezone: //网站时区,保持默认就可以了 更改完后,我们回 GitBash 命令行,输入以下命令: 1$ hexo g && hexo s 再重新打开浏览器刷新,是不是页面语言变成中文的了! 好吧,现在先说一下上面的命令中的 hexo s && hexo g 是什么东西,hexo s 是启动 hexo 服务的,可以理解为是 hexo serve 或者 hexo start ,一般都是简写为 hexo s ,而 hexo g 是重新生成 public 文件夹的命令,全称是 hexo generator ,那么 public 文件夹是什么呢? 别慌,我们回头看一下 hexo 文件夹目录: 看到这个 public 文件夹嘛,这个里面就是生成的所有静态文件,包括 html,css,js 文件以及图片等,稍微懂一些前端的人就知道这个文件夹就是你的博客被 node.js 渲染后生成的最终文件夹,这个文件夹中点开 index.html 就能看到你的博客页面了😄!简单点来说如果在本地修改了什么文件内容,可以使用 hexo g 命令重新生成一下public文件夹,那么这个文件夹只要修改了,再使用 hexo s 启动服务就可以看到页面变化了,当然 hexo s 本身就具有在线调试的功能,如果 hexo s 无法刷新页面修改内容,那么使用 hexo g && hexo s 就可以看到更改内容啦~ 第一篇博客文章 博客基本框架完成后,我们做的第一件事就是创建第一篇个人博客啦!可以在 Gitbash 中 cd 到 hexo 根目录,使用如下命令: 12345hexo new "我的第一篇博客"//或者可以简写为hexo n "我的第一篇博客" 就可以在 hexo/source/_posts 文件下面新建一个 .md 文件,这个 .md 文件就是 Markdown 文件,所以我们写博客只要在本地编辑这个 Markdown 文件就可以了。Markdown 语法可以 Google 一下使用教程,比较简单并且实用。 将 Hexo 博客部署到 Github 上 为什么要部署到 Github上呢,当然是有以下原因: 因为 Hexo 博客都是静态文件,GithubPages 自身就支持静态文件。 免费方便,不用花一分钱就可以自己搭建一个自由的个人博客,并且没有服务器没有后台。 可以随意绑定自己的域名,并且可以一键开启 HTTPS,很方便。 数据绝对安全,github 可以恢复任意版本。 博客内容可以轻松打包、转移以及发布到其它平台。 …… 在部署到 Github 之前,我们需要准备好自己的 Github 账号,Github 账号可以在 Github 官网注册。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Github官网https://github.com 创建 Github 仓库 首先我们需要在 Github 上创建一个 repository,就是创建一个仓库的意思,在登录账号后首页就有一个显眼的 create new repository,点进去就可以看到了,注意名字必须为你的 Github用户名.github.io ,如下图: 不能设置为其它名字,只能用这个仓库名。这样设置以后,我们在不绑定域名的前提下,可以直接使用 http://Sanarous.github.io 来访问自己的个人博客,这样就相当于有一个个人域名,并且是永久免费的! 配置 SSH Key 如果是第一次在自己的本机上使用 Git 上传到 Github 上,那么必须配置 SSH key ,表示 Github 允许这台机器有权限使用 Git 上传代码到远端仓库。 我们可以在 GitBash 中使用 $ cd ~/.ssh 命令来查看本机已经存在的 ssh 密钥,如果是第一次使用会显示 No such file or directory ,如果不是的话,就需要用已经存在的密钥或者重新生成一份了。 然后输入 1$ ssh-keygen -t rsa -C "邮件地址" 上面的邮件地址就是你的 Github 注册邮箱地址,在提示后连续回车,最终会生成一个文件在 C 盘用户目录下面 打开这个 .ssh 文件,里面存放了密钥,然后我们打开自己的 Github 个人主页,进入 个人设置 -> SSH and GPG keys -> New SSH key 上面的 Title 可以任意填写,下面的 key 注意要打开 .ssh 文件,将密钥复制进去 测试是否成功 使用如下命令: 1$ ssh -T [email protected] # 注意邮箱地址不用改 如果提示 Are you sure you want to continue connecting (yes/no)? 点击yes,会显示 Hi Sanarous! You've successfully authenticated, but GitHub does not provide shell access. 看到这个信息就说明 SSH 已经配置成功! 配置 Git 提交的用户信息 上面配置成功后,我们就可以设置 Git 的全局用户信息了,这个信息设置当前 Git 命令上传代码的用户信息。 使用以下命令: 12$ git config --global user.name "XuxuGood" // 你的github用户名,非昵称$ git config --global user.email "[email protected]" // 填写你的github注册邮箱 配置完成后以后提交代码都是使用的这个用户信息进行提交的。 将 Hexo 博客部署到 Github 上 首先打开 hexo 站点的配置文件,找到 deloy 并填写如下配置: 文件位置:hexo/_config.xml 1234deploy: type: git repository: [email protected]:XuxuGood/XuxuGood.github.io.git # 用户名改成你自己的 branch: master 或者也可以写成这样: 1234deploy: type: github repository: https://github.com/XuxuGood/XuxuGood.github.io.git branch: master 无论是哪种写法,此时直接执行 hexo d 的话一般会报如下错误:Deployer not found: github 或者 Deployer not found: git 原因是还需要安装一个插件 hexo-deployer-git ,我们可以在 Git 中 cd 到 hexo 根目录,并输入以下命令: 12cd /e/hexo/npm install hexo-deployer-git --save 然后我们可以使用 hexo clean && hexo g && hexo deploy 命令来完成一键部署到 Github 上。 安装 Next 主题 由于默认的主题 landscape 界面比较丑(当然魔改也是可以的),我们可以使用 Hexo 官方推荐的主题:https://hexo.io/themes ,如果喜欢这里面的某些主题,可以直接去上面主题对应的界面中的 Github 中下载(下载方式与稍后介绍的下载 Next 主题一样)。 目前在 Github 上 star 数量比较多的有 Next 主题、yillia 主题等,Next 主题基本占据了 Hexo 博客的半壁江山,并且 Next 主题集成功能较多,因此我下面只介绍 Next 主题的使用方式。 首先我们可以打开 Github,全局搜索 hexo-next ,我们会发现是有两个 star 数量比较高的: info,其中第一个是 Next5 版本的,而第二个是 Next6 版本的。2019/7/27 更新:第二个 Github 仓库目前一直是 Next 主题更新最新版本的的仓库,目前已经更新到 v7.2.0,以下内容仅供参考。 注意上面说的版本关系,在 Next6 版本上其实增加了很多 Next5 需要手动配置的东西,并且 Next5 已经停止维护了,如果不太喜欢自己手动去配置的话,建议使用 Next6 版本。当然本博客使用的仍然是 Next5 版本,如果需要我的个性化设置的话也可以留言。 下面我们来安装 Next 主题。 下载 Next 主题 以下均以 Next6 版本为例,Next5 版本操作基本一样。 进入https://github.com/theme-next/hexo-theme-next 我们安装 Next6 版本的话,有两种方式: 在 GitBash 中 cd 到博客根目录下,然后使用 git clone 命令将 Next 仓库克隆到 hexo 目录下的 themes/next ,即命令是 git clone https://github.com/theme-next/hexo-theme-next.git themes/next 直接在 Github 页面上选择绿色的按钮 Clone or Download ,点击下载 zip 压缩包。 如果第一种方式比较慢的话,可以直接选择第二种方式直接下载,然后将下载好的压缩包解压后放在 E:\\blog\\hexo\\themes\\ 下面,git clone 的话也是在这个下面,下载好后的名字可以任取。 设置 hexo 博客为 Next 主题 在站点根目录下_config.xml配置文件中,找到如下代码并进行配置: 文件位置:hexo/_config.xml 12345# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/- theme: landscape # 更改原始默认的主题名称,修改为如下+ theme: hexo-theme-next # 此处填入你在themes目录下的next主题文件名 测试 Next 主题 在 GitBash 中输入 1$ hexo clean && hexo g && hexo s 等待启动完成在浏览器中输入http://localhost:4000即可查看安装好的 Next 主题! Next 主题基本功能配置 Next 主题安装好后的初始界面也是很简洁的,我们可以先设置一些常用功能😊。 首先为了防止懵逼,再次友情提醒:在这里修改的一律是主题配置文件 _config.xml ,目录是 hexo/themes/next/_config.xml ,千万不要走错地方了! 我们打开 _config.xml 主题配置文件对应一一修改。 danger,由于配置文件是 yml,如果对 yml 语法不太熟的小伙伴,注意每个配置之间都必须有空格,不然报错。如 override: false ,注意英文冒号 : 后面有一个空格,所有 yml 语法都是这样。 以下默认使用的文本编辑器为 notepad++、WebStorm 或者 sublime,主题使用的是 Next6 版本(注意 Next 官方会不断进行更新,所以下面的不一定都有效,因为官方正在不断集成更多功能,具体地可以自行在配置文件中探索~) 以下均在 hexo s 在线调试环境中进行修改~ 网站favicon图标设置 favicon 图标相当于是网站的 logo 简化版,所以我们也可以给自己的网站添加一个 favicon 图标,使用notepad++、WebStorm 或者 sublime 打开配置文件后,使用 Ctrl+F 搜索 favicon 文件位置:hexo/themes/next/_config.xml 1234567favicon: small: /images/favicon-16x16-next.png # 网站小图标 medium: /images/favicon-32x32-next.png # 中等图标 apple_touch_icon: /images/apple-touch-icon-next.png # app_touch上显示图标 safari_pinned_tab: /images/logo.svg # 在Safari浏览器中显示图标 #android_manifest: /images/manifest.json # 安卓默认显示同普通情况下 #ms_browserconfig: /images/browserconfig.xml 网站 favicon 图标可以放到你的 next 主题目录下面的 source/images 中,然后按照上述相对路径方式引用,这里 favicon 图标不需要非得是 ico 格式的,也可以是 png 或其它图片格式。 如果是有第三方图床放图片链接的,也可以直接改成图片链接。 网站页脚小心心定义 在 Next5 版本中需要手动修改设置,但是在 Next6 中已经集成好这个功能了,搜索 footer,设置如下,就可以在页脚看到跳动的小心心啦 文件位置:hexo/themes/next/_config.xml 12345678910111213footer: # 指定网站开始运行的年份,如果不指定,则默认为当前年份 since: 2019 # 在页脚年份和版权信息中间显示的图标 icon: # 图标形状显示,可以参考 https://fontawesome.com/v4.7.0/icons 中的名字 # 官方建议使用 heart 图标,颜色设置为 #ff0000,这也显示的是跳动的心 name: heart # 如果想要图标跳动,下面设置为 true animated: true # 改变图标颜色 color: "#ff0000" 关闭底部由 hexo 强力驱动的广告 由于默认的页面中 hexo 会在网页底部居中位置打个广告(要恰饭的嘛),但是官方还是很良心的在配置文件中设置了广告开关,还是在上面 footer 中,如下配置全部设置为 false 就可以关闭广告了。 1234567891011 powered: # Hexo link (Powered by Hexo).- enable: false # Version info of Hexo after Hexo link (vX.X.X).- version: false theme: # Theme & scheme info link (Theme - NexT.scheme).- enable: false # Version info of NexT after scheme info (vX.X.X).- version: false 菜单栏设置 在配置文件中搜索 menu,找到如下: 1234567891011121314menu: home: / || home #about: /about/ || user #tags: /tags/ || tags #categories: /categories/ || th archives: /archives/ || archive #schedule: /schedule/ || calendar #sitemap: /sitemap.xml || sitemap #commonweal: /404/ || heartbeat# Enable/Disable menu icons.menu_icons: enable: true #表示是否显示菜单图标icons badges: false # 显示每个菜单下面有多少个内容 其中 || 后面表示的 Fontawesome 中的图标名称,如果想要修改图标,可以去FontAwesome官网找自己喜欢的图标样式,前面部分 /about/ 是表示该菜单的相对链接,比如网站主页访问是 https://bestzuo.cn ,那么点击这个菜单栏的链接就变成了 https://bestzuo.cn/about/ 这种形式。 如果需要增加菜单栏的话,可以在 Gitbash 中输入以下命令: 12345hexo new page "photos"//或者可以间写为如下形式hexo n page "photos" 这样就会自动在 hexo/source 目录下生成一个文件夹,而且里面是一个 index.md 文件,Node.js 最终会把这个 md 文件渲染成 html 文件,所以菜单栏中内容就在这个 md 文件中写就可以,并且文章内支持 html 写法,所以具有很强的扩展性🤣,创建之后,就需要在配置文件这个地方添加上新增菜单的名称以及链接格式和 FontAwesome 图标,然后打开 hexo/themes/next/languages/zh-CN.yml 文件,在 menu 下面按照格式汉化你的菜单栏名称~ Next主题四种风格设置 Next 主题支持四种内置风格,每个人喜欢的风格都不同,可以在基础风格上继续进行魔改。搜索 schema,找到如下代码: 12345# Schemesscheme: Muse#scheme: Mist#scheme: Pisces#scheme: Gemini 四种风格样式可以自己进行切换,可以在本地 hexo g && hexo s 后在线调试这个地方的代码,喜欢哪个就选哪个吧~ 社交链接设置 社交链接主要是在侧边栏中展示的,一般都是自己的各种网站主页。搜索 social,找到如下代码: 1234567891011#social: #GitHub: https://github.com/yourname || github #E-Mail: mailto:[email protected] || envelope #Google: https://plus.google.com/yourname || google #Twitter: https://twitter.com/yourname || twitter #FB Page: https://www.facebook.com/yourname || facebook #VK Group: https://vk.com/yourname || vk #StackOverflow: https://stackoverflow.com/yourname || stack-overflow #YouTube: https://youtube.com/yourname || youtube #Instagram: https://instagram.com/yourname || instagram #Skype: skype:yourname?call|chat || skype 后面的 yourname 改成你自己在对应网站的 ID,然后将前面的 # 注释去掉。其中 || 后面还是表示该链接前面的图标在 FontAwesome 中的名称,可以进行自定义修改。 友情链接设置 友情链接也是处于侧边栏中的,这里可以完全自定义内容,一般可以用来放社交圈子链接(不过一般博客都会单独做一个页面放其它人的博客友链)。搜索 links,找到如下配置 1234567# Blog rollslinks_icon: linklinks_title: Linkslinks_layout: block#links_layout: inline#links: #Title: http://example.com/ 这里可以添加你想要的友情链接,比如可以添加百度链接: 12345678# Blog rollslinks_icon: link # 链接对应的Fontawesome图标名称links_title: Links # 设置链接标题,可以自定义links_layout: block # 图标布局方式,有inline和block两种#links_layout: inlinelinks: Baidu: https://www.baidu.com/ Github: https://github.com/ 侧边栏设置 搜索 sidebar,找到如下配置 1234567891011121314151617181920212223sidebar: # Sidebar Position, available value: left | right (only for Pisces | Gemini). # 此处设置只适用于Pisces或者Gemini风格 position: left #position: right # 侧边栏如何展示 display: post # 侧边栏在打开文章的时候显示 #display: always # 侧边栏不管在哪都显示 #display: hide # 隐藏侧边栏 #display: remove # 移除侧边栏 # Sidebar offset from top menubar in pixels (only for Pisces | Gemini). offset: 12 # 设置返回页面顶部设置,只适用于Pisces或者Gemini风格,建议开启 b2t: true # 显示浏览百分比,建议开启 scrollpercent: true # Enable sidebar on narrow view (only for Muse | Mist). onmobile: false 文章开启阅读更多按钮 如果不开启阅读更多按钮的话,默认是展示文章中所有内容的,这显然体验不好。 一般都会在文章中插入 <!--more--> 这种注释形式表示首页展示到注释处为止。或者会使用如下官方配置文件中自带的方式。一般都推荐使用注释的方式,因为下面这种 auto_excerpt 方式不会保留前面的行文样式,但是注释方式会保留样式。 搜索 auto_excerpt,找到如下: 123auto_excerpt: enable: true length: 150 #到多少字数后不显示 默认是关闭的,也就是首页上默认显示整篇文章,而为了显示阅读更多按钮,我们可以开启这个服务。 文章元数据设置 元数据就是显示在 home 页的文章创建于、更新于、阅读次数之类的数据,搜索 post_meta,找到如下配置: 12345678post_meta: item_text: true # 是否显示对应的文字 created_at: true # 是否显示 创建于 updated_at: # 是否显示 更新于 enabled: false # 更新日期显示规则,只有更新日期与创建日期不同时,才会显示 another_day: true categories: true # 是否显示分类信息 上面应该已经说明的很详细了吧~ 文章字数统计设置 由于上面元数据中没有带统计文章字数功能,所以需要利用插件来生成,搜索 post_wordcount,找到如下配置: 12345678# Post wordcount display settings# Dependencies: https://github.com/theme-next/hexo-symbols-count-timesymbols_count_time: separated_meta: true item_text_post: true item_text_total: false awl: 4 wpm: 275 注意开启上述设置必须要添加 hexo-symbols-count-time 模块依赖,即在 hexo 站点根目录下使用 npm install hexo-symbols-count-time --save 命令安装模块后开启上述功能使用。 侧边栏头像设置 侧边栏中没有博主头像是没有灵魂的。Next6 主题中自带鼠标放在头像上能旋转 360度的功能,如果是 Next5 的话需要自己手动配置。 搜索 avatar,找到如下配置 12345678# Sidebar Avataravatar: # 如果放在本地(source/images): /images/avatar.gif # 如果第三方图床,直接写地址 url: # 此处是头像的地址 rounded: true # 设置头像是否为圆形 opacity: 1 # 设置不透明度,1为完全不透明,0为完全透明 rotated: true # 设置鼠标放到头像上是否旋转 代码块设置 Next6 中自带了复制代码按钮,Next5 需要自己手动配置。 搜索 codeblock,找到如下配置: 12345codeblock: border_radius: 8 # 按钮圆滑度 copy_button: # 设置是否开启代码块复制按钮 enable: true show_result: true # 是否显示复制成功信息 开启文章打赏按钮 一篇辛辛苦苦敲出来的文章,不妨开启一下文章打赏功能,万一真有人给你棒棒糖呢😆 ~ 搜索 reward,找到如下配置并修改: 12345# Rewardreward_comment: 坚持原创技术分享,感谢您的支持和鼓励!wechatpay: # 微信收款图片地址alipay: # 支付宝收款图片地址#bitcoin: /images/bitcoin.png # 比特币 开启相关文章推荐功能 要优化读者体验的话,可以在读者阅读完一篇文章后,能自动推荐相关内容的文章,不仅能考虑读者感受,还能给自己博客文章带来阅读量,岂不是一举两得😆 ? 搜索 related_posts,找到如下配置: 12345678910related_posts: enable: true # 是否开启 title: 相关文章推荐 # 标题 display_in_home: false # 是否在首页显示,建议为false params: maxCount: 5 # 相关文章的最大数量 #PPMixingRate: 0.0 #isDate: false #isImage: false #isExcerpt: false 开启相关文章推荐需要安装 hexo-related-popular-posts 模块,即在 hexo 站点根目录下使用 npm install hexo-related-popular-posts --save 安装模块,然后开启上面的相关文章功能就可以啦~ 开启文章版本信息 搜索 post_copyright,找到配置 1234post_copyright: enable: false license: CC BY-NC-SA 3.0 license_url: https://creativecommons.org/licenses/by-nc-sa/3.0/ 设置为 true 可以打开,这样在每篇文章最后都会有版权提示。 代码块风格设置 搜索 highlight_theme,有以下多种风格: 1234# Code Highlight theme# Available values: normal | night | night eighties | night blue | night bright# https://github.com/chriskempson/tomorrow-themehighlight_theme: night eighties 可以自己修改上面 normal 、night 、 night eighties 、 night blue 、night bright 在线调试选择自己喜欢的风格。 添加valine评论系统 没有评论系统的博客是没有灵魂的,不仅如此,当前免费开源的评论系统中,valine 因为简洁并且支持匿名留言得到很多博主的喜爱,而像其它的来必力(韩国的)、Gitalk(Github的)等都有这样那样的缺点,不太建议使用。 需要注意的是valine后台评论保存是依赖于 leancloud的,leancloud 是一个面向个人用户免费的存储系统(当然不止是提供存储功能,还有其它服务),我们需要在 leancloud 官网注册,具体步骤可以看valine的官方文档介绍。 搜索 valine,找到如下配置: 12345678910valine: enable: true appid: # your leancloud application appid appkey: # your leancloud application appkey notify: false # mail notifier , https://github.com/xCss/Valine/wiki verify: false # Verification code placeholder: 留下邮箱,有回复时你将收到提醒,邮箱不会被公开。 # comment box placeholder avatar: wavatar # gravatar style https://valine.js.org/avatar/ guest_info: nick,mail # custom comment header default: nick,mail,link pageSize: 10 # pagination size 注意由于 valine 依赖于 leancloud 存储服务,因此要先去 https://leancloud.cn 网站注册,获取到 appid 和 appkey 后放到这里就 ok 了。其中 avatar 是设置默认头像,可以去 https://valine.js.org/avatar 选择默认头像,然后在这里设置名字即可。具体使用可以参考valine的官方文档。 开启分享按钮 百度分享对国内网站来说更友好一些,搜索 baidushare,找到如下代码: 12baidushare: type: button # 设置分享按钮的风格,有button何slide形式 将注释去掉打开即可,虽然说是默认不支持 https 格式,但是网上有解决方案,可以 Google 一下。 如果嫌麻烦的话,也可以使用下面支持 https 的 needmoreshare。如果要开启 needmoreshare 的话,可以搜索 needmoreshare2,找到如下代码: 12345678910111213141516needmoreshare2: enable: false postbottom: enable: false options: iconStyle: box boxForm: horizontal position: bottomCenter networks: Weibo,Wechat,Douban,QQZone,Twitter,Facebook float: enable: false options: iconStyle: box boxForm: horizontal position: middleRight networks: Weibo,Wechat,Douban,QQZone,Twitter,Facebook 注意 needmoreshare2 是依赖 theme-next-needmoreshare2 模块的,可以去 https://github.com/theme-next/theme-next-needmoreshare2 找到使用方法。 设置文章阅读量 搜索 leancloud_visitors,并进行如下配置: 123456789leancloud_visitors: enable: true app_id: app_key: # Dependencies: https://github.com/theme-next/hexo-leancloud-counter-security # If you don't care about security in lc counter and just want to use it directly # (without hexo-leancloud-counter-security plugin), set the `security` to `false`. security: false betterPerformance: true 这个功能依赖 hexo-leancloud-counter-security 模块,需要安装该插件。 注意这个 appid 和 appkey 跟上面开启 valine 评论使用的 leanCloud 是一样的,但是需要在 leancloud 中创建 classes 对象存储,具体方式可以 Google 一下。 开启不蒜子统计功能 目前不蒜子统计网站统计做的还可以,网站访问量主要是分为 pv 和 uv 两种,pv 是指页面访问量,每访问一次或者刷新一次页面后该页面的 pv+1,而 uv 是指独立 ip 访问量,就是说一天内同一 ip 访问一个页面 N 次,uv 都只是 + 最开始的那一次。一般用 pv 作为页面的访问量,uv 作为页面的访客量。 搜索 busuanzi_count,可以配置如下,也可以使用默认设置。 1234567891011121314# Show Views/Visitors of the website/page with busuanzi.# Get more information on http://ibruce.info/2015/04/04/busuanzi/busuanzi_count: enable: true site_uv: true #total visitors site_uv_icon: #user-circle site_uv_header: 你是来访的第 site_uv_footer: 位小伙伴 site_pv: false #total views site_pv_icon: eye site_pv_header: 访问次数: site_pv_footer: 次 post_views: false post_views_icon: eye 注意其中的 post_views 与上面的 leanCloud_visitors 冲突,两者都是显示文章阅读量,只开启一个就可以了。 开启本地博客搜索功能 提升读者用户体验,博客内肯定是需要一个全局搜索按钮的。当然hexo已经集成了几款开源的搜索插件,一般都使用的是 local_search。 搜索 local_search,设置代码如下: 1234567891011# Local search# Dependencies: https://github.com/theme-next/hexo-generator-searchdblocal_search: enable: true # if auto, trigger search by changing input # if manual, trigger search by pressing enter key or search button trigger: auto # show top n results per article, show all results by setting to -1 top_n_per_article: 1 # unescape html strings to the readable one unescape: false` 注意该搜索功能需要依赖 hexo-generator-searchdb 插件,依然还是使用命令 npm install hexo-generator-searchdb --save 来进行安装。然后 在 hexo 站点根目录配置文件 _config.xml 的末尾,加入以下代码即可。 12345search: path: search.xml field: post format: html limit: 10000 修改加载特效 由于网页不可能一直都秒进,总会等待一段时间的,所以可以修改一下加载的特效。Next 已经集成了很多加载特效,可以在下面选项中在线调试测试一下。 搜索 pace,找到如下代码: 12345678910111213141516171819# Progress bar in the top during page loading.pace: false# Themes list:#pace-theme-big-counter#pace-theme-bounce#pace-theme-barber-shop#pace-theme-center-atom#pace-theme-center-circle#pace-theme-center-radar#pace-theme-center-simple#pace-theme-corner-indicator#pace-theme-fill-left#pace-theme-flash#pace-theme-loading-bar#pace-theme-mac-osx#pace-theme-minimal# For example# pace_theme: pace-theme-center-simplepace_theme: pace-theme-center-radar #默认设置,可以修改为上述任何一个 可以自己修改后使用 hexo s 本地调试挑选自己喜欢的加载样式。 开启3D背景 给博客添加 3D 背景特效,说实话我只在最开始折腾博客的时候开过,后来为了提升响应速度,这些不必要的东西都给关闭了。 配置文件中代码如下: 1234567891011# Canvas-nestcanvas_nest: false# three_wavesthree_waves: false# canvas_linescanvas_lines: false# canvas_spherecanvas_sphere: false 默认为 false,可以自己逐一设置为 true 然后在本地调试查看3D效果。 Next主题进阶优化配置 在介绍完 Next 主题的基本配置后,下面才是本文的重头戏,在 Next 进阶配置时,一定需要你懂一些 CSS 和基本的前端知识,不然报错了可能你根本无从寻找问题原因,或者在进行大面积修改前先将主题备份一份,这样出错后找不到原因还能及时止损😂 。 学会使用浏览器F12定位样式 发现页面有大量留白?颜色不合自己口味?那就 F12 开始吧,大换装开始!空白区?颜色?背景?圆角矩形?阴影?透明度?超链接样式?侧栏头像圆形并旋转?文章标题前面的竖线和颜色?只需按下 F12,改到自己想要的样式,然后 Copy 到 custom.styl 文件即可。感觉这是 NexT 主题非常棒的设计,因为这让我们能够很方便自定义博客的样式。怎么知道要修改这个文件呢? 强烈推荐阅读这篇文章。 怎么修改? 浏览器按 F12 即可,建议用 Google Chrome 浏览器(有梯子的直接去 Google 下载😂),或者火狐浏览器。因为这两个浏览器属于标准浏览器,如果你按下 F12 后简直特么一脸懵逼,那么别急,硬着头皮慢慢折腾吧哈哈哈嗝~ 快速懵逼到熟悉 首先按下 F12 后的操作流程图,就是这篇文章中的三步骤,点小箭头定位元素,调试 CSS 代码,最后 Copy 到 custom.styl 。然后懵逼的地方,应该有下面两点: 按下 F12 后弹出的界面是什么鬼?! 界面中的 {} 前面的和里面的英文是什么鬼?! 第一点:弹出的界面是为调试设计的,如果你知道调试的是啥,也许就自然了解弹出的界面,所以我不多说,不过还是给一份 Google 官方的资料——Chrome 开发者工具。第二点:{} 前面的是 HTML 的元素名,{} 里面的是这个元素的 CSS 样式。 社交要先有自己原则,一段代码要先声明变量,一个数学问题有前提,一篇论文要先定义名词,到这里我们也必须要先了解一些 HTML 和 CSS 的基本语法知识了,才能继续折腾下去。建议先浏览下 MDN 的 HTML 和 CSS 的页面,但没必要记住里面的每一个语法知识,因为这样的记忆是不够深刻也并不高效的,只要浏览下留个印象(为了能找准元素)就行,而记忆是要在实践中记忆的。 附上我的cutom.styl 由于原代码比较长,我在另外一篇博客中已经有专门分享,注意千万不要照搬到你的 custom.styl !一定要先找对应元素再修改,为了方便大家查看我已经做了一定的注释,仅供大家参考! 修改博客字体 博客影响美观的除了样式,就是直接映入读者眼睛的字体了,因此选择一款优雅的字体对博客美感的提升是非常大的,当然,博客字体大小是可以直接修改的: 文件位置:hexo/themes/next/source/css/_variables/base.styl 1$font-size-base = 16px 如果你对字体的选择比较感兴趣,推荐阅读: Web 中文字体排版指南 Web 字体的选择和运用 如何优雅的选择默认字体(font-family) 中文字体网页开发指南 在 Web 内容中使用系统字体 首先对于汉字来说,因为其字体库太大,通常都是调用本地中文字体库。然而,不同设备有不同默认中文字体和中文字体库,想要尽可能在不同设备上有较好的显示效果,就要在调用不同设备的本地字体库中显示效果较好的中文字体。下面附上我参考的大佬的字体选择: 文件位置:hexo/themes/next/source/css/_variables/base.styl 1234567891011121314151617// Font families.$font-family-chinese = "Noto Serif SC"$font-family-base = $font-family-chinese, sans-serif$font-family-base = get_font_family('global'), $font-family-chinese, sans-serif if get_font_family('global')$font-family-logo = $font-family-base$font-family-logo = get_font_family('logo'), $font-family-base if get_font_family('logo')$font-family-headings = $font-family-base$font-family-headings = get_font_family('headings'), $font-family-base if get_font_family('headings')$font-family-posts = $font-family-base$font-family-posts = get_font_family('posts'), $font-family-base if get_font_family('posts')$font-family-monospace = consolas, Menlo, $font-family-chinese, monospace$font-family-monospace = get_font_family('codes'), consolas, Menlo, $font-family-chinese, monospace if get_font_family('codes') 注意:要想 NexT 主题的简体中文字体配置生效,站点配置文件中的 language 必须为 zh-CN。然后对于英文字体,因为其字体库很小,所以想要个性化就简单多了。首先去 Google Fonts 找自己喜欢的英文字体,然后编辑主题配置文件,可以查看一下 NexT 官方文档(最新版)。下面附上我参考的大佬的英文字体选择: 文件位置:hexo/themes/next/_config.yml 1234567891011121314151617181920212223242526272829303132333435363738394041424344font: enable: true # Uri of fonts host. E.g. //fonts.googleapis.com (Default). # Google 字体 国内镜像 host: # Font options: # `external: true` will load this font family from `host` above. # `family: Times New Roman`. Without any quotes. # `size: xx`. Use `px` as unit. # Global font settings used on <body> element. global: external: true# family: Lato family: EB Garamond # 字体参考:https://io-oi.me/tech/noto-serif-sc-added-on-google-fonts/#main size: 16 # Font settings for Headlines (h1, h2, h3, h4, h5, h6). # Fallback to `global` font settings. headings: external: true family: size: # Font settings for posts. # Fallback to `global` font settings. posts: external: true family: # Font settings for Logo. # Fallback to `global` font settings. logo: external: true family: size: # Font settings for <code> and code blocks. codes: external: true family: size: 其它字体设置可以参考这篇文章。 博客推广及优化 想要自己写的博客能被别人看到?希望能得到别人的评论肯定?渴望分享技术?那么博客推广肯定是必不可少了😙。 手动推广 大概就是在其它博客或者视频等信息流下面留下自己的博客地址,比如第一件事咱们可以去 next 主题专门的博客分享的issue区留下自己的爪印,或者在搜索引擎中搜索使用 hexo+next 搭建博客的热门教程中,在评论区留下地址,这样就可以手动引流啦。 搜索引擎SEO收录 当然手动引流不是长久之计,搜索引擎是互联网上寻找资源的重要手段,而要让别人能够在搜索结果中看到自己的博客文章链接,就必须让搜索引擎收录,怎么操作呢? 可以直接参考这篇文章,写的很详细,学会自己使用站长工具抓取自己的网页,然后请求搜索引擎收录 ,查看收录量可以在百度或者 Google 中使用 site:hasaik.com 即site:后面加上域名的方式,如果你是使用 Github Pages,由于百度是默认不抓取 Github 的,所以也需要使用上述方式进行提交。 其它优化可以看我的另外一篇博客。 间接影响 除了直接被搜索引擎收录之外,如果有其它被搜索引擎收录的文章中,引用你的某篇文章的链接地址,那么同样可以引流到你的博客,这种称为间接影响,不要小看间接影响,如果你的博客写的很好,经常被引用的话,那么间接影响带来的流量是非常巨大的,但是其中会有一个问题,通过“引流”到达的流量,你需要尽可能地将用户留在自己的博客上,那么如何吸引用户呢?当然是有两个方面: 博客装饰美观 文章质量高 读者的第一印象往往是读者需要阅读的内容的质量,如果质量达到要求,那么读者会注意到博客的界面,如果界面很特别的话,那么读者肯定是马上加入了标签,并且选择多停留一下继续浏览博客的其它内容,那么这里就会引出另外一个问题,就是博客的响应速度,如果读者点击某篇文章或者按钮后响应了半天空白,那么你猜他会怎么做?肯定是直接右上角了啊,所以博客的响应速度一定要优化好。 如果做到上面三点,那么就算好不容易「骗」到一个浏览量,但是这个读者马上被博客和文章惊呆了,看完文章后,这读者心里美滋滋,认为这么好的文章(博客)必须分享啊🌚,于是可能马上来了一大批满怀期待的读者,然后这批读者又……这时文章的读者数(博客的访问量)就不是简单的加法了~ 知识平台 直接或间接因为 Google 这样的搜索引擎而来的读者,绝大部分都是技术人员,而他们只希望尽快解决自己的技术问题,这也是他们的目的,这就意味着博客上的一首诗还是很难被欣赏。而要想照亮他人,他人必须要能懂自己的文章,这样也才可能有更强的交互——评论。所以为了不浪费自己的光能,能把自己的光能完完整整地贡献给文明,那就必须也让一首诗也有评论,怎么做呢?让读者的类型多样化,不限于技术人员。还好现在大部分读者也不用搜索引擎了,谁在吞食搜索引擎的用户?移动端。智能手机的迅速普及导致搜索引擎已经不是人们获取知识的主要途径,大部分人已经将手机 APP 上的知识平台作为自己获取知识的主要途径,比如:知乎、简书、微信订阅号……所以,你还可以将自己的文章发布在这些知识平台上的相应分类上,然后留个博客链接,吸引更多类型的读者😄~ 谷歌分析 你怎么知道自己推广的效果?你怎么知道有没有人看了自己的博客?哪篇文章最受欢迎?此时有没有人正浏览着自己的博客?自己的文章有没有被引用?这时最常用的就是强大免费的 Google Analytics,推荐博客建好后,就立即使用。 如何使用?请务必自备梯子查看 Google 官方的教程,开始使用后一定要按照里面的设置,先添加多份 view(数据视图)。 文章底部加上评分小星星 淘宝买东西,作为消费者的我们,看评价很重要。现在作为博主,写了一篇文章,很期待读者的反馈。而与淘宝一样,确认收货后,相比评论,更愿意五星好评。那么博客文章怎么加上呢?首先打开主题配置文件: 文件位置:hexo/themes/next/_config.yml 123456# Star rating support to each article.# To get your ID visit https://widgetpack.comrating: enable: true id: color: f79533 先去注释中的网站,首页点 Rating,然后注册个帐号,填一下自己博客的信息,左上角有个 ID,填进主题配置文件中就行,color 改成自己喜欢的即可。另: 可以配置评分方式,侧栏 > Rating > Setting,建议用 IP address 或 Device(cookie),免登录,毕竟 Socials 里面的选项几乎都被墙,不适合国内网络环境。 建议在侧栏 > Site > Setting 中勾选 Private 选项。 上面两步勾选后别忘了点击页面右下方的 SAVE SETTING 绿色按钮保存。 如果感觉上下留白太多,咋整?浏览器 F12 找元素,调成自己喜欢的值,然后 Copy 到 custom.styl 即可。经过上面的配置,默认最下面只会显示 5 颗小星星,简洁但不明了😂,怎么加上文字说明呢?编辑下面这个文件,Ctrl + F 搜索 rating ,找到这段,对比我给出的,在绿色这行所示的位置,加上自己想要的说明和样式即可。 文件位置:hexo/themes/next/layout/_macro/post.swig 123456{% if theme.rating.enable %} <div class="wp_rating">+ <div style="color: rgba(0, 0, 0, 0.75); font-size:13px; letter-spacing:3px">(&gt;看完记得五星好评哦亲&lt;)</div> <div id="wpac-rating"></div> </div>{% endif %} 为站点添加标题崩溃特效 该特效为:当用户离开站点相关的页面时,网页的标题会变成已崩溃,网站图标也会改变;当用户重新回到站点页面时才会恢复正常。 实现方式: 在 /themes/next/source/js/src/ 目录下新建 crash_cheat.js ,代码如下: 1234567891011121314151617181920$(window).load(function () {//整合页面欺骗特效 window.onload有冲突 var OriginTitile = document.title; var titleTime; document.addEventListener('visibilitychange', function () { if (document.hidden) { $('[rel="icon"]').attr('href', "../../images/failure.png"); $('[rel="shortcut icon"]').attr('href', "../../images/failure.png"); document.title = '(つェ⊂) 我藏好了哦~ '; clearTimeout(titleTime); } else { $('[rel="icon"]').attr('href', "../../images/favicon.png"); $('[rel="shortcut icon"]').attr('href', "../../images/favicon.png"); document.title = 'o(^▽^)o 被你发现啦~ '; titleTime = setTimeout(function () { document.title = OriginTitile; }, 2000); } });}); 在 /themes/next/layout/_layout.swig 文件末尾(ps:我相信各位引入js的位置应该都知道),添加引用: 12<!--崩溃欺骗--><script type="text/javascript" src="/js/src/crash_cheat.js"></script> 上面的图片放在 /themes/next/source/images/ 目录下,自行选择喜欢的图片即可。 每篇文章末尾添加致谢 在 hexo/themes/next/layout/_macro 中新建一个 passage-end-tag.swig 文件,并添加如下内容: 12345<div> {% if not is_index %} <div class="read-over">-------------------本文结束 <i class="fa fa-paw"></i> 感谢您的阅读-------------------</div> {% endif %}</div> 接着打开 \\themes\\next\\layout\\_macro\\post.swig 文件,在 post-body 之后, post-footer 之前添加如下画绿色部分代码(post-footer 之前两个 div): 1234567+ <div>+ {% if not is_index %}+ {% include 'passage-end-tag.swig' %}+ {% endif %}+ </div><footer class="post-footer"> 最后,在主题配置文件下,在末尾添加: 文件位置:hexo/themes/next/_config.xml 123# 文章末尾添加"本文结束"标记passage_end_tag: enabled: true 新增文章时自动打开Markdown编辑器 由于每次在 GitBash 中使用 hexo n "文章名称" 时还要自己去本地目录中打开编辑器,这对于懒癌患者来说实在是太麻烦了😂,那么不如实现一个监听的 js 代码监听新建文章的命令,只要监听到了就自动打开相应的 Markdown编辑器,这样不就方便多了嘛! 首先在 hexo/scripts 下新建一个 newpost.js 文件,如果没有 scripts 文件可以手动创建一个。 如果你是 windows 用户,在这个文件中写入如下代码: 1234var spawn = require('child_process').exec;hexo.on('new', function(data){ spawn('start "markdown编辑器绝对路径.exe" ' + data.path);}); 如果是 mac 用户,就写入如下代码: 1234var exec = require('child_process').exec;hexo.on('new', function(data){ exec('open -a "markdown编辑器绝对路径.app" ' + data.path);}); 注意里面要修改的是 Markdown 编辑器的绝对路径,我使用的是 Typora ,所以我的绝对路径是 E:\\\\Typora\\\\bin\\\\Typora.exe ,大家可以对应进行修改。 使用hexo-admin在线发布文章 最近有不少小伙伴问我这个博客如何在线发布文章,毕竟大多数人都是程序小白,不想使用 GitBash 命令行敲各种命令。所以也有大神做一个 hexo-admin 管理工具,虽然这个东西没有适配完全,但是对于文章管理的基本功能够用。 .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}hexo-admin官方地址https://github.com/jaredly/hexo-admin 官方在线演示地址:https://jaredforsyth.com/hexo-admin/admin/#/ 要使用这个工具,首先需要安装插件: 1npm install --save hexo-admin 安装完成后,要启动的话,使用 hexo s -d 命令,然后打开网址 localhost:4000/admin/ 进行登录就可以管理后台了。 warning,注意,安装 hexo-admin 插件后,启动后台页面时 GitBash 可能还是会报错,这是由于 hexo-admin 自身还依赖很多个插件,报错信息上只要提示缺少 module “xxx”时,你只要继续使用上面的命令安装缺少的那个插件xxx就可以了,我当时好像连续装了十几个插件才最终启动成功🌚… 安装好后,还需要设置登录的账号密码,不然谁都可以使用你的后台管理。第一次登录后,进入 setting 菜单,点击 Setup authentification here 进入用户名密码设置项,按照提示设置后,把生成的代码添加到 hexo/_config.xml 中,如: 安装好后,还需要设置登录的账号密码,不然谁都可以使用你的后台管理。第一次登录后,进入 setting 菜单,点击 Setup authentification here 进入用户名密码设置项,按照提示设置后,把生成的代码添加到 hexo/_config.xml 中,如: 文件位置:hexo/_config.xml 12345# hexo-admin authentificationadmin: username: xuxu password_hash: $2a$10$anOUoIka5uKSupfpNtu6IOSPvsj2OTPOXC2qpewkP1DmrTZr39Va6 secret: my blog 其中密码是在你设置后进行加密的。 最后在线 deploy 时,可以打开 Deploy 菜单,第一次点击 Deploy 时会提示 Error: Config value "admin.deployCommand" not found ,这个问题作者已经解决,在上面的配置中添加一行配置: 12admin: deployCommand: './hexo-deploy.sh' 就可以在线部署到 Github 了! Hexo-abbrlink生成唯一文章链接 一个 Hexo插件 ,用于根据帖子标题生成静态帖子链接。 首先需要安装插件,博客站点下执行下面命令: 1npm install hexo-abbrlink --save danger,执行此命令可能会不成功,提示你缺少相应的依赖,比如babel-eslint、mini-css-extract-plugin、webpack-cli…使用npm命令安装即可,比如 npm install [email protected] babel-eslint@8 --save-dev 修改站点配置文件 config.yml 文件中的永久链接: 12- permalink: year/:month/:day/:title/+ permalink: posts/:abbrlink.html 在 permalink 下面写入下面的内容: 1234# abbrlink configabbrlink: alg: crc32 # 算法:crc16(default) and crc32 rep: hex # 进制:dec(default) and hex 示例:https://hasaik.com/posts/ab21860c.html ,其中 ab21860c.html 就是生成的永链。 修改侧栏滚动条样式 默认的侧栏滚动条其实挺丑的,添加如下代码重新渲染页面就可以修改侧栏滚动条了。 文件位置:hexo/themes/next/source/css/_custom/custom.styl 1234567891011121314151617181920212223242526272829303132333435/*更好的侧边滚动条*/::-webkit-scrollbar { width: 10px; height: 10px;}::-webkit-scrollbar-button { width: 0; height: 0;}::-webkit-scrollbar-button:start:increment,::-webkit-scrollbar-button:end:decrement { display: none;}::-webkit-scrollbar-corner { display: block;}::-webkit-scrollbar-thumb { border-radius: 8px; background-color: rgba(0,0,0,.2);}::-webkit-scrollbar-thumb:hover { border-radius: 8px; background-color: rgba(0,0,0,.5);}::-webkit-scrollbar-track,::-webkit-scrollbar-thumb { border-right: 1px solid transparent; border-left: 1px solid transparent;}::-webkit-scrollbar-track:hover { background-color: rgba(0,0,0,.15);}::-webkit-scrollbar-button:start { width: 10px; height: 10px; /*background: url(../images/scrollbar_arrow.png) no-repeat 0 0;*/ /*可以添加滚动条样式*/} 侧栏加入已运行的时间 我们都有自己的生日,都知道自己的岁数,那为什么不给博客加上,让读者知道博客的年纪呢?操作很简单,而且不是精确到年而是精确到秒,233333~ 首先加入以下代码: 文件位置:hexo/themes/next/layout/_custom/sidebar.swig 123456789101112131415161718192021222324252627<div id="days"></div><script>function show_date_time(){ window.setTimeout("show_date_time()", 1000); BirthDay=new Date("05/27/2017 15:13:14"); today=new Date(); timeold=(today.getTime()-BirthDay.getTime()); sectimeold=timeold/1000 secondsold=Math.floor(sectimeold); msPerDay=24*60*60*1000 e_daysold=timeold/msPerDay daysold=Math.floor(e_daysold); e_hrsold=(e_daysold-daysold)*24; hrsold=setzero(Math.floor(e_hrsold)); e_minsold=(e_hrsold-hrsold)*60; minsold=setzero(Math.floor((e_hrsold-hrsold)*60)); seconds=setzero(Math.floor((e_minsold-minsold)*60)); document.getElementById('days').innerHTML="已运行 "+daysold+" 天 "+hrsold+" 小时 "+minsold+" 分 "+seconds+" 秒";}function setzero(i) { if (i<10) { i="0" + i }; return i;}show_date_time();</script> 上面 Date 的值记得改为你自己的,且按上面格式,然后修改: 文件位置:hexo/themes/next/layout/_macro/sidebar.swig 1234567891011121314151617181920 {# Blogroll #} {% if theme.links %} <div class="links-of-blogroll motion-element {{ "links-of-blogroll-" + theme.links_layout | default('inline') }}"> <div class="links-of-blogroll-title"> <i class="fa fa-fw fa-{{ theme.links_icon | default('globe') | lower }}"></i> {{ theme.links_title }}&nbsp; <i class="fa fa-fw fa-{{ theme.links_icon | default('globe') | lower }}"></i> </div> <ul class="links-of-blogroll-list"> {% for name, link in theme.links %} <li class="links-of-blogroll-item"> <a href="{{ link }}" title="{{ name }}" target="_blank">{{ name }}</a> </li> {% endfor %} </ul>+ {% include '../_custom/sidebar.swig' %} </div> {% endif %}- {% include '../_custom/sidebar.swig' %} 这样就可以了!当然,要是不喜欢颜色,感觉不好看,就可以在上文所提的 custom.styl 加入: 文件位置:hexo/themes/next/source/css/_custom/custom.styl 1234567/*自定义的侧栏时间样式*/#days { display: block; color: rgb(7, 179, 155); font-size: 13px; margin-top: 15px;} 里面的值 F12 调成自己喜欢的,然后更改即可。要是不想放在侧栏,想放在页脚,自己应该能折腾了吧😄~ 添加博客热门文章页面 博客已有的分类,如 categories 和 tags,都是基于博主的,那么有没有一种分类是基于读者的呢?有,一种是搜索,另一种就是这里的文章阅读量排行榜。前提是在主题配置文件中配置了 leancloud_visitors,配置方法在基础配置中已经介绍过了。首先新建页面: 所在目录:hexo/ 1hexo new page "top" 然后在主题配置文件中加上菜单 top 和它的 icon: 文件位置:hexo/themes/next/_config.yml 12menu: top: /top/ || signal 接着在语言翻译文件中加上菜单 top: 文件位置:hexo/themes/next/languages/zh_Hans.yml 1234567891011menu: home: 首页 archives: 归档 categories: 分类 tags: 标签 about: 关于 search: 搜索 schedule: 日程表 sitemap: 站点地图 commonweal: 公益404 top: 热门排行 /* 可以不为 热门排行,随便取 */ 注意:如果你的站点配置文件中的 languages 写的不是 zh-CN,那么这里请更改相应语言配置文件。最后,编辑第一步新建页面生成的文件: 文件位置:hexo/source/top/index.md 1234567891011121314151617181920212223242526272829303132333435363738title: 文章热度排行comments: falsedate: 2019-11-03 14:37:48type:---<div id="top" style="margin-top:30px;"></div><script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><!-- <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.4.js"></script> --><script>AV.initialize("app_id", "app_key");</script><script type="text/javascript"> var time = 0 var title = "" var url = "" var query = new AV.Query('Counter'); query.notEqualTo('id', 0); query.descending('time'); query.limit(1000); query.find().then(function (todo) { for (var i = 0; i < 1000; i++) { var result = todo[i].attributes; time = result.time; title = result.title; url = result.url; var content = "<p class='my-article-top'>" + "<font color='#a7a7e5'>" + "➤【热度: " + "</font>" + "<font color='#f1a8ce'>" + time + " ℃】" + "</font>" + "<a href='" + url + "'>" + title + "</a>" + "</p>"; document.getElementById("top").innerHTML += content } }, function (error) { console.log("error"); });</script><style>.post-description { display: none; }</style> 必须将里面的里面的 app_id 和 app_key 替换为你的主题配置文件中的值,必须替换里面博客的链接,1000是显示文章的数量,其它可以自己看情况更改。最后,修改样式可以在 custom.styl 中加入自定义代码,不过还有几点需要注意: 如果在设置 > 安全中心中,没有将http://localhost:4000加入 Web 安全域名,那么本地调试将看不到,可以先将之加入,调试完后删除。 如果你发现文章标题显示不对,这是由于更改过文章标题导致的,在存储 > Counter 双击title修改即可。 注意:如果你的博客使用了 Valine 评论系统,那么可能会有代码冲突问题,解决方法可 Google ~ 文章置顶功能 由于博客的首页可能是被浏览最多的页面,所以首页的前几篇文章被阅读的可能性比较大。可以利用这个特点,通过将自己认为重要的文章放在首页,从而让重要的文章被阅读的可能性增大😄。但是,默认的排序只有一个维度——时间,两种选择——正序和倒序,这就造成自己的得意之作被埋没了,怎么办呢,如何实现文章的置顶? NexT 主题以前有过这个功能,然而由于一些 bugs(issue)被去掉了。不过在这个丰富的 issue 中,我自己摸索出了一种解决方法,参考了 issue 中的那篇文章。 首先移除默认安装的插件: 所在目录:hexo/ 1npm uninstall hexo-generator-index --save 然后安装新插件: 1npm install hexo-generator-index-pin-top --save 最后编辑有这需求的相关文章时,在Front-matter(文件最上方以—分隔的区域)加上一行: 1top: true 然后就行了。如果你置顶了多篇,怎么控制顺序呢?设置top的值(大的在前面),比如: 1234567# Post a.mdtitle: atop: 1# Post b.mdtitle: btop: 10 那么文章 b 便会显示在文章 a 的前面。可是,没有任何标记啊,读者怎么知道文章置顶了😂~还好 NexT 原有的置顶功能有考虑到这个,且置顶的样式没有被移除,所以可以直接利用,编辑文件加入以下代码: 文件位置:/themes/next/layout/_macro/post.swig 12345678<div class="post-meta"> <span class="post-time">+ {% if post.top %}+ <i class="fa fa-thumb-tack"></i>+ <font color=7D26CD>置顶</font>+ <span class="post-meta-divider">|</span>+ {% endif %} 精品文章 在 /themes/next/layout/_macro/ 路径,找到 post.swig ,在前 文置 顶功能后边,加上如下代码: 123456{% if post.essential%} <span class="post-meta-item-icon"> <i class="fa fa-newspaper-o jingping">精品</i> </span> <span class="post-meta-divider">|</span> {% endif %} 在 themes/next/source/css/_custom/custom.styl 中,增加如下样式: 12345.jingping{ background : #00a8c3; padding:2px 4px 2px 4px; color: #fff;} 在需要设置精品的文章md文件中,加入如下代码: 1essential: true 添加近期文章版块 在 next/layout/_macro/sidebar.swig 中的 if theme.links 对应的 endif 后面添加以下代码: 123456789101112131415161718<!--近期文章版块 began--> {% if theme.recent_posts %} <div class="links-of-blogroll motion-element {{ "links-of-blogroll-" + theme.recent_posts_layout }}"> <div class="links-of-blogroll-title"> <i class="fa fa-history fa-{{ theme.recent_posts_icon | lower }}" aria-hidden="true"></i> {{ theme.recent_posts_title }} </div> <ul class="links-of-blogroll-list"> {% set posts = site.posts.sort('-date') %} {% for post in posts.slice('0', '5') %} <li class='my-links-of-blogroll-li'> <a href="{{ url_for(post.path) }}" title="{{ post.title }}" target="">{{ post.title }}</a> </li> {% endfor %} </ul> </div> {% endif %}<!--近期文章版块 end--> 为了配置方便,在主题的 _config.yml 中添加了几个变量,如下: 123recent_posts_title: 近期文章recent_posts_layout: blockrecent_posts: true 代码块复制功能 依赖 clipboard.js 实现,个性化配置可参考官方文档。在 /themes/next/layout/_layout.swig 引入下载的 JS 123<!-- 代码块复制功能 --><script type="text/javascript" src="/js/src/clipboard.min.js"></script><script type="text/javascript" src="/js/src/clipboard-use.js"></script> 位于 /themes/next/source/js/src/ 目录下的 clipboard.min.js 和 clipboard-use.js 代码分别如下: 1234567/*! * clipboard.min.js v2.0.4 * https://zenorocha.github.io/clipboard.js * * Licensed MIT © Zeno Rocha */!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}}(),a=o(n(1)),c=o(n(3)),u=o(n(4));function o(t){return t&&t.__esModule?t:{default:t}}var l=function(t){function o(t,e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,o);var n=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,(o.__proto__||Object.getPrototypeOf(o)).call(this));return n.resolveOptions(e),n.listenClick(t),n}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(o,c.default),i(o,[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===r(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,u.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new a.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return s("action",t)}},{key:"defaultTarget",value:function(t){var e=s("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return s("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),o}();function s(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}t.exports=l},function(t,e,n){"use strict";var o,r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}}(),a=n(2),c=(o=a)&&o.__esModule?o:{default:o};var u=function(){function e(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),this.resolveOptions(t),this.initSelection()}return i(e,[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,c.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,c.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var e=void 0;try{e=document.execCommand(this.action)}catch(t){e=!1}this.handleResult(e)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),e}();t.exports=u},function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n},function(t,e,n){var d=n(5),h=n(6);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!d.string(e))throw new TypeError("Second argument must be a String");if(!d.fn(n))throw new TypeError("Third argument must be a Function");if(d.node(t))return s=e,f=n,(l=t).addEventListener(s,f),{destroy:function(){l.removeEventListener(s,f)}};if(d.nodeList(t))return a=t,c=e,u=n,Array.prototype.forEach.call(a,function(t){t.addEventListener(c,u)}),{destroy:function(){Array.prototype.forEach.call(a,function(t){t.removeEventListener(c,u)})}};if(d.string(t))return o=t,r=e,i=n,h(document.body,o,r,i);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,a,c,u,l,s,f}},function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e,n){var a=n(7);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=a(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},function(t,e){if("undefined"!=typeof Element&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}}])}); 123456789101112131415161718/*页面载入完成后,创建复制按钮*/!function (e, t, a) { /* code */ var initCopyCode = function(){ var copyHtml = ''; copyHtml += '<button class="btn-copy" data-clipboard-snippet="">'; //fa fa-globe可以去字体库替换自己想要的图标 copyHtml += ' <i class="fa fa-clipboard"></i><span>复制</span>'; copyHtml += '</button>'; $(".highlight .code pre").before(copyHtml); new ClipboardJS('.btn-copy', { target: function(trigger) { return trigger.nextElementSibling; } }); } initCopyCode();}(window, document); 可根据需要在 /themes/next/source/css/_custom/custom.styl 加 CSS 1234567891011121314151617181920212223242526272829303132333435363738.highlight{ //position: relative; position: static;}highlight-wrap { background: #008b89;}.btn-copy { display: inline-block; cursor: pointer; background-color: #eee; background-image: linear-gradient(#fcfcfc,#eee); border: 1px solid #d5d5d5; border-radius: 3px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-appearance: none; font-size: 13px; font-weight: 700; line-height: 20px; color: #333; -webkit-transition: opacity .3s ease-in-out; -o-transition: opacity .3s ease-in-out; transition: opacity .3s ease-in-out; padding: 2px 6px; position: absolute; right: 5px; top: 5px; opacity: 0;}.btn-copy span { margin-left: 5px;}.highlight:hover .btn-copy{ opacity: 1;} 博客加入canvas粒子时钟 这是一款很有意思的 HTML5 Canvas 时间动画,总体来说,它是一个可以和客户端同步的时钟,其特点是当时间走动时,数字将会散落成一个个粒子动画。 在 /themes/next/layout/_custom/ 目录下,新建 clock.swig 文件,内容如下: 123456<div id=""> <canvas id="canvas" style="width:60%;"></div><!--粒子时钟js--><script type="text/javascript" src="/js/src/canvas-dance-time.js"></script> 在 /themes/next/layout/_macro/sidebar.swig 中引入: 1{% include '../_custom/clock.swig' %} 可根据自己的偏好来设置具体位置,我是加在了侧栏的末尾。 在 /themes/next/source/js/src 目录下,新建 canvas-dance-time.js 文件,内容如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282(function(){ var WINDOW_WIDTH = 820; var WINDOW_HEIGHT = 250; var RADIUS = 7; //球半径 var NUMBER_GAP = 10; //数字之间的间隙 var u=0.65; //碰撞能量损耗系数 var context; //Canvas绘制上下文 var balls = []; //存储彩色的小球 const colors = ["#33B5E5","#0099CC","#AA66CC","#9933CC","#99CC00","#669900","#FFBB33","#FF8800","#FF4444","#CC0000"]; //彩色小球的颜色 var currentNums = []; //屏幕显示的8个字符 var digit = [ [ [0,0,1,1,1,0,0], [0,1,1,0,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,0,1,1,0], [0,0,1,1,1,0,0] ],//0 [ [0,0,0,1,1,0,0], [0,1,1,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [1,1,1,1,1,1,1] ],//1 [ [0,1,1,1,1,1,0], [1,1,0,0,0,1,1], [0,0,0,0,0,1,1], [0,0,0,0,1,1,0], [0,0,0,1,1,0,0], [0,0,1,1,0,0,0], [0,1,1,0,0,0,0], [1,1,0,0,0,0,0], [1,1,0,0,0,1,1], [1,1,1,1,1,1,1] ],//2 [ [1,1,1,1,1,1,1], [0,0,0,0,0,1,1], [0,0,0,0,1,1,0], [0,0,0,1,1,0,0], [0,0,1,1,1,0,0], [0,0,0,0,1,1,0], [0,0,0,0,0,1,1], [0,0,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,1,1,0] ],//3 [ [0,0,0,0,1,1,0], [0,0,0,1,1,1,0], [0,0,1,1,1,1,0], [0,1,1,0,1,1,0], [1,1,0,0,1,1,0], [1,1,1,1,1,1,1], [0,0,0,0,1,1,0], [0,0,0,0,1,1,0], [0,0,0,0,1,1,0], [0,0,0,1,1,1,1] ],//4 [ [1,1,1,1,1,1,1], [1,1,0,0,0,0,0], [1,1,0,0,0,0,0], [1,1,1,1,1,1,0], [0,0,0,0,0,1,1], [0,0,0,0,0,1,1], [0,0,0,0,0,1,1], [0,0,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,1,1,0] ],//5 [ [0,0,0,0,1,1,0], [0,0,1,1,0,0,0], [0,1,1,0,0,0,0], [1,1,0,0,0,0,0], [1,1,0,1,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,1,1,0] ],//6 [ [1,1,1,1,1,1,1], [1,1,0,0,0,1,1], [0,0,0,0,1,1,0], [0,0,0,0,1,1,0], [0,0,0,1,1,0,0], [0,0,0,1,1,0,0], [0,0,1,1,0,0,0], [0,0,1,1,0,0,0], [0,0,1,1,0,0,0], [0,0,1,1,0,0,0] ],//7 [ [0,1,1,1,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,1,1,0] ],//8 [ [0,1,1,1,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [0,1,1,1,0,1,1], [0,0,0,0,0,1,1], [0,0,0,0,0,1,1], [0,0,0,0,1,1,0], [0,0,0,1,1,0,0], [0,1,1,0,0,0,0] ],//9 [ [0,0,0,0], [0,0,0,0], [0,1,1,0], [0,1,1,0], [0,0,0,0], [0,0,0,0], [0,1,1,0], [0,1,1,0], [0,0,0,0], [0,0,0,0] ]//: ]; function drawDatetime(cxt){ var nums = []; context.fillStyle="#005eac" var date = new Date(); var offsetX = 70, offsetY = 30; var hours = date.getHours(); var num1 = Math.floor(hours/10); var num2 = hours%10; nums.push({num: num1}); nums.push({num: num2}); nums.push({num: 10}); //冒号 var minutes = date.getMinutes(); var num1 = Math.floor(minutes/10); var num2 = minutes%10; nums.push({num: num1}); nums.push({num: num2}); nums.push({num: 10}); //冒号 var seconds = date.getSeconds(); var num1 = Math.floor(seconds/10); var num2 = seconds%10; nums.push({num: num1}); nums.push({num: num2}); for(var x = 0;x<nums.length;x++){ nums[x].offsetX = offsetX; offsetX = drawSingleNumber(offsetX,offsetY, nums[x].num,cxt); //两个数字连一块,应该间隔一些距离 if(x<nums.length-1){ if((nums[x].num!=10) &&(nums[x+1].num!=10)){ offsetX+=NUMBER_GAP; } } } //说明这是初始化 if(currentNums.length ==0){ currentNums = nums; }else{ //进行比较 for(var index = 0;index<currentNums.length;index++){ if(currentNums[index].num!=nums[index].num){ //不一样时,添加彩色小球 addBalls(nums[index]); currentNums[index].num=nums[index].num; } } } renderBalls(cxt); updateBalls(); return date; } function addBalls (item) { var num = item.num; var numMatrix = digit[num]; for(var y = 0;y<numMatrix.length;y++){ for(var x = 0;x<numMatrix[y].length;x++){ if(numMatrix[y][x]==1){ var ball={ offsetX:item.offsetX+RADIUS+RADIUS*2*x, offsetY:30+RADIUS+RADIUS*2*y, color:colors[Math.floor(Math.random()*colors.length)], g:1.5+Math.random(), vx:Math.pow(-1, Math.ceil(Math.random()*10))*4+Math.random(), vy:-5 } balls.push(ball); } } } } function renderBalls(cxt){ for(var index = 0;index<balls.length;index++){ cxt.beginPath(); cxt.fillStyle=balls[index].color; cxt.arc(balls[index].offsetX, balls[index].offsetY, RADIUS, 0, 2*Math.PI); cxt.fill(); } } function updateBalls () { var i =0; for(var index = 0;index<balls.length;index++){ var ball = balls[index]; ball.offsetX += ball.vx; ball.offsetY += ball.vy; ball.vy+=ball.g; if(ball.offsetY > (WINDOW_HEIGHT-RADIUS)){ ball.offsetY= WINDOW_HEIGHT-RADIUS; ball.vy=-ball.vy*u; } if(ball.offsetX>RADIUS&&ball.offsetX<(WINDOW_WIDTH-RADIUS)){ balls[i]=balls[index]; i++; } } //去除出边界的球 for(;i<balls.length;i++){ balls.pop(); } } function drawSingleNumber(offsetX, offsetY, num, cxt){ var numMatrix = digit[num]; for(var y = 0;y<numMatrix.length;y++){ for(var x = 0;x<numMatrix[y].length;x++){ if(numMatrix[y][x]==1){ cxt.beginPath(); cxt.arc(offsetX+RADIUS+RADIUS*2*x,offsetY+RADIUS+RADIUS*2*y,RADIUS,0,2*Math.PI); cxt.fill(); } } } cxt.beginPath(); offsetX += numMatrix[0].length*RADIUS*2; return offsetX; } var canvas = document.getElementById("canvas"); canvas.width=WINDOW_WIDTH; canvas.height=WINDOW_HEIGHT; context = canvas.getContext("2d"); //记录当前绘制的时刻 var currentDate = new Date(); setInterval(function(){ //清空整个Canvas,重新绘制内容 context.clearRect(0, 0, context.canvas.width, context.canvas.height); drawDatetime(context); }, 50)})(); 自定义文章底部版权声明 效果图: 在目录 themes/next/layout/_macro/ 下添加 my-copyright.swig ,内容如下: 123456789101112131415161718192021222324252627282930{% if page.copyright %}<div class="my_post_copyright"> <script src="//cdn.bootcss.com/clipboard.js/1.5.10/clipboard.min.js"></script> <!-- JS库 sweetalert 可修改路径 --> <script src="https://cdn.bootcss.com/jquery/2.0.0/jquery.min.js"></script> <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script> <p><span>本文标题:</span><a href="{{ url_for(page.path) }}">{{ page.title }}</a></p> <p><span>文章作者:</span><a href="/" title="访问 {{ theme.author }} 的个人博客">{{ theme.author }}</a></p> <p><span>发布时间:</span>{{ page.date.format("YYYY年MM月DD日 - HH:mm:ss") }}</p> <p><span>最后更新:</span>{{ page.updated.format("YYYY年MM月DD日 - HH:mm:ss") }}</p> <p><span>原始链接:</span><a href="{{ url_for(page.path) }}" title="{{ page.title }}">{{ page.permalink }}</a> <span class="copy-path" title="点击复制文章链接"><i class="fa fa-clipboard" data-clipboard-text="{{ page.permalink }}" aria-label="复制成功!"></i></span> </p> <p><span>许可协议:</span><i class="fa fa-creative-commons"></i> <a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank" title="Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)">署名-非商业性使用-禁止演绎 4.0 国际</a> 转载请保留原文链接及作者。</p></div><script> var clipboard = new Clipboard('.fa-clipboard'); $(".fa-clipboard").click(function(){ clipboard.on('success', function(){ swal({ title: "", text: '复制成功', icon: "success", showConfirmButton: true }); }); });</script>{% endif %} 在目录 themes/next/source/css/_common/components/post/ 下添加 my-post-copyright.styl,内容如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859.my_post_copyright { width: 85%; max-width: 45em; margin: 2.8em auto 0; padding: 0.5em 1.0em; border: 1px solid #d3d3d3; font-size: 0.93rem; line-height: 1.6em; word-break: break-all; background: rgba(255, 255, 255, 0.4);}.my_post_copyright p { margin: 0;}.my_post_copyright span { display: inline-block; width: 5.2em; color: #b5b5b5; font-weight: bold;}.my_post_copyright .raw { margin-left: 1em; width: 5em;}.my_post_copyright a { color: #808080; border-bottom: 0;}.my_post_copyright a:hover { color: #0593d3; text-decoration: underline;}.my_post_copyright:hover .fa-clipboard { color: #000;}.my_post_copyright .post-url:hover { font-weight: normal;}.my_post_copyright .copy-path { margin-left: 1em; width: 1em; +mobile() { display: none; }}.my_post_copyright .copy-path:hover { color: #808080; cursor: pointer;} 修改 themes/next/layout/_macro/post.swig ,如下: 123456789101112131415{#####################}{### END POST BODY ###}{#####################}+<div>+ {% if not is_index %}+ {% include 'my-copyright.swig' %}+ {% endif %}+</div>{% if theme.wechat_subscriber.enabled and not is_index %} <div> {% include 'wechat-subscriber.swig' %} </div>{% endif %} danger,以上 + 号后面的为新增代码 打开 themes/next/source/css/_common/components/post/post.styl 文件,在最后一行增加代码: 1@import "my-post-copyright" 设置新建文章自动开启 copyright ,即新建文章自动显示自定义的版权声明,设置 ~/scaffolds/post.md 文件,如下: 1234title: {{ title }}date: {{ date }}copyright: true #新增,开启--- 博客动态背景图片 在 themes/next/source/css/_custom/custom.styl 中添加CSS样式 1234567891011/* hexo next主题下,自动更换背景图片 began *//* 图片来源https://source.unsplash.com/ */body { background: url(https://source.unsplash.com/random/1920x1080); background-repeat: no-repeat; background-attachment: fixed; background-position: 50% 50%;}/* hexo next主题下,自动更换背景图片 end */ 博客写作进阶 Next主题集成了很多好看的写作样式,具体可以看我的另外一篇博客。 插入音乐和视频 音乐的话,网易云音乐的外链很好用,不仅有可以单曲,还能有歌单,有兴趣的自己去网易云音乐找首歌尝试。但是目前有很多音乐因为版权原因放不了,还有就是不完全支持 https,导致浏览器地址栏的小绿锁不见了。要解决这些缺点,就需要安装插件👽。 音乐 直接用 HTML 的标签,写法如下: 1<audio src="https://什么什么什么.mp3" style="max-height :100%; max-width: 100%; display: block; margin-left: auto; margin-right: auto;" controls="controls" loop="loop" preload="meta">Your browser does not support the audio tag.</audio> 用插件,有显示歌词功能,也美观。首先在站点文件夹根目录安装插件: 1npm install hexo-tag-aplayer --save 然后文章中的写法: 1{% aplayer "歌曲名" "歌手名" "https://什么什么什么.mp3" "https://封面图.jpg" "lrc:https://歌词.lrc" %} 另外可以支持歌单: 1234567891011121314151617181920212223{% aplayerlist %}{ "autoplay": false, "showlrc": 3, "mutex": true, "music": [ { "title": "歌曲名", "author": "歌手名", "url": "https://什么什么什么.mp3", "pic": "https://封面图.jpg", "lrc": "https://歌词.lrc" }, { "title": "歌曲名", "author": "歌手名", "url": "https://什么什么什么.mp3", "pic": "https://封面图.jpg", "lrc": "https://歌词.lrc" } ]}{% endaplayerlist %} 里面的详细参数见 README 和这插件的「母亲」Aplayer 的官方文档。关于 LRC 歌词,可以用工具下载网易云音乐的歌词,另发现暂时不支持 offset 参数。当然,如果那歌词很操蛋,有错误(比如字母大小写和标点符号乱加)或者时间完全对不上,然后你也和我一样是个完美主义者,那接下来就是令人窒息的操作了,一句一句自己查看修改…… 什么,你想把网易云的几百首歌手动同步到博客😨?慢慢慢,有一种非常简单的方法,此这种方法也支持单曲,将参数里的 playlist 更改为 song 即可,非常建议食用!更多功能请仔细阅读 README。 视频 直接用 HTML 的标签,写法如下: 1<video poster="https://封面图.jpg" src="https://什么什么什么.mp4" style="max-height :100%; max-width: 100%; display: block; margin-left: auto; margin-right: auto;" controls="controls" loop="loop" preload="meta">Your browser does not support the video tag.</video> 用插件,可支持弹幕,首先在站点文件夹根目录安装插件: 1npm install hexo-tag-dplayer --save 然后文章中的写法: 1{% dplayer "url=https://什么什么什么.mp4" "https://封面图.jpg" "api=https://api.prprpr.me/dplayer/" "id=" "loop=false" %} 要使用弹幕,必须有 api 和 id 两项,并且若使用的是官方的 api 地址(即上面的),id 的值不能与这个列表的值一样。id 的值自己随便取,唯一要求就是前面这点。如果唯一要求难倒了你,可以使用这个工具将一段与众不同的文字😂生成一段看起来毫无意义的哈希值,这样看起来是不是好多了。 当然,这个插件的功能还有很多,可以去 README 和这插件的「母亲」Dplayer 的官方文档看看。 主题代码块高亮 发现一款类似 MacPanel 的代码块高亮样式,具体可以看我的另外一篇博客。 主题头像旋转功能 将头像显示成圆形,鼠标放上去有旋转效果。 找到 /themes/next/source/css/_common/components/sidebar/sidebar-author.styl 添加以下代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455/* 添加头像旋转 */.site-author-image { display: block; margin: 0 auto; padding: $site-author-image-padding; max-width: $site-author-image-width; height: $site-author-image-height; border: $site-author-image-border-width solid $site-author-image-border-color; /* 头像圆形 */ border-radius: 80px; -webkit-border-radius: 80px; -moz-border-radius: 80px; box-shadow: inset 0 -1px 0 #333sf; /* 设置循环动画 [animation: (play)动画名称 (2s)动画播放时长单位秒或微秒 (ase-out)动画播放的速度曲线为以低速结束 (1s)等待1秒然后开始动画 (1)动画播放次数(infinite为循环播放) ]*/ /* 鼠标经过头像旋转360度 */ -webkit-transition: -webkit-transform 1.0s ease-out; -moz-transition: -moz-transform 1.0s ease-out; transition: transform 1.0s ease-out;}img:hover { /* 鼠标经过停止头像旋转 -webkit-animation-play-state:paused; animation-play-state:paused;*/ /* 鼠标经过头像旋转360度 */ -webkit-transform: rotateZ(360deg); -moz-transform: rotateZ(360deg); transform: rotateZ(360deg);}/* Z 轴旋转动画 */@-webkit-keyframes play { 0% { -webkit-transform: rotateZ(0deg); } 100% { -webkit-transform: rotateZ(-360deg); }}@-moz-keyframes play { 0% { -moz-transform: rotateZ(0deg); } 100% { -moz-transform: rotateZ(-360deg); }}@keyframes play { 0% { transform: rotateZ(0deg); } 100% { transform: rotateZ(-360deg); }} 实现图片点击放大效果 next 主题自带 facybox 图片放大功能,首先推荐使用 fancybox,如果不想用可以使用以下自定义的图片放大功能,首先创建 images.js 文件如下: 目录位置:hexo/themes/next/source/js/src/image.js 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189let container = document.documentElement||document.body;let img,div,src,btnleft,btnright;var imgid=0;let x,y,w,h,tx,ty,tw,th,ww,wh;let closeMove=function(){ if(div==undefined){ return false; } div.style.opacity=0; img.style.height=h+"px"; img.style.width=w+"px"; img.style.left=x+"px"; img.style.top=(y - container.scrollTop)+"px"; // 延迟移除dom setTimeout(function(){ div.remove(); img.remove(); btnright.remove(); btnleft.remove(); },100);};let closeFade=function(){ if(div==undefined){ return false; } div.style.opacity=0; img.style.opacity=0; // 延迟移除dom setTimeout(function(){ div.remove(); img.remove(); btnright.remove(); btnleft.remove(); },100);};// 监听滚动关闭层document.addEventListener("scroll",function(){ closeFade();});document.querySelectorAll("img").forEach(v=>{ if (v.parentNode.localName!=a) { v.id=imgid; imgid++; v.addEventListener("click",function(e){ // 注册事件 // 记录小图的位置个大小 x=e.target.offsetLeft; y=e.target.offsetTop; w=e.target.offsetWidth; h=e.target.offsetHeight; data-src=e.target.src; id=e.target.id; // 创建遮罩层 div=document.createElement("div"); div.style.cssText=` position:fixed; left:0; top:0; bottom:0; right:0; background-color: rgba(25,25,25,0.8); z-index:99999999; transition:all .3s cubic-bezier(0.165, 0.84, 0.44, 1); `; document.body.appendChild(div); setTimeout(function(){ div.style.opacity=1; },0); // (此处可以加loading) // 创建副本 img=new Image(); btnright=document.createElement("button"); btnleft=document.createElement("button"); img. data-src=src; btnleft.style.cssText=` position:fixed; border-radius: 50%;; left:${x - 20}px; top:${y - container.scrollTop + h/2}px; width:50px; height:50px; border: 0px; background-color: rgba(200,200,200,0.8); font-size: 20px; z-index: 999999999; transition:all .3s cubic-bezier(0.165, 0.84, 0.44, 1); `; btnright.style.cssText=` position:fixed; border-radius: 50%; left:${x + w + 20}px; top:${y - container.scrollTop + h/2}px; width:50px; border: 0px; height:50px; font-size: 20px; background-color: rgba(200,200,200,0.8); z-index: 999999999; transition:all .3s cubic-bezier(0.165, 0.84, 0.44, 1); `; btnleft.innerText="<"; btnright.innerText=">"; img.style.cssText=` position:fixed; border-radius: 12px; left:${x}px; top:${y - container.scrollTop}px; width:${w}px; height:${h}px; z-index: 999999999; transition:all .3s cubic-bezier(0.165, 0.84, 0.44, 1); opacity:0; `; btnleft.onclick=function(){ if(id===0){ alert("已经是第一张了!"); return; } var left=document.getElementById(id-1); img. data-src=left.src; x=left.offsetLeft; y=left.offsetTop; w=left.offsetWidth; h=left.offsetHeight; id--; } btnright.onclick=function(){ id++; if(id>=imgid){ alert("已经是最后一张了!"); return; } var right=document.getElementById(id); img. data-src=right.src; x=right.offsetLeft; y=right.offsetTop; w=right.offsetWidth; h=right.offsetHeight; } img.onload=function(){ document.body.appendChild(img); document.body.appendChild(btnright); document.body.appendChild(btnleft); // 浏览器宽高 wh=window.innerHeight; ww=window.innerWidth; // 目标宽高和坐标 if(w/h<ww/wh){ th=wh-80; tw=w/h*th >> 0; tx=(ww - tw) / 2; ty=40; } else{ tw=ww*0.8; th=h/w*tw >> 0; tx=ww*0.1; ty=(wh-th)/2; } // 延迟写入否则不会有动画 setTimeout(function(){ img.style.opacity=1; img.style.height=th+"px"; img.style.width=tw+"px"; img.style.left=tx+"px"; img.style.top=ty+"px"; btnleft.style.left=(tx-90)+"px"; btnleft.style.top=(ty+th/2)+"px"; btnright.style.left=(tx+tw+40)+"px"; btnright.style.top=(ty+th/2)+"px"; // 点击隐藏 div.onclick=img.onclick=closeMove; },10); }; });//end event }});//end forEach 然后在 hexo/themes/layout/_layout.swig 中最下面 <script></script> 中加入如下引入: 文件位置:hexo/themes/layout/_layout.swig 1<script type="text/javascript" src="/js/src/image.js"></script> 给博客添加豆瓣读书/电影/游戏页面 作为一个有内涵的博客…咳咳…展示自己丰富的阅读量是很有必要的,豆瓣读书就是这么一个很好的平台,当然已经有作者利用爬虫将豆瓣读书/电影/游戏内容爬取下来,并制作成了 hexo 博客插件,具体可以看官方文档README,当然也可以看我的另外一篇博客。 当然作者所做的界面是没有样式的,我们可以在作者的基础上继续魔改增加自己喜欢的样式,在安装好所需插件后,我们打开目录为 hexo/node_modules/hexo-douban/lib/templates/index.css 文件,里面是整个阅读界面的 css 样式代码文件,我们可以在其中添加背景图片等样式,比如可以添加如下: 文件位置:hexo/node_modules/hexo-douban/lib/templates/index.css 12345678.main { padding-bottom: 150px; margin-top: 0px; background-image:url("xxx.jpg"); background-size: cover; background-attachment: fixed; background-repeat:no-repeat;} 修改上面的 url 中地址图片链接就可以新增背景图片了~ 增加二次元看板娘live2d模型 如果你喜欢二次元,或者想给博客增加一个动态装饰,那么看板娘肯定是你的不二之选了,看板娘原生使用文档在这里 ,但是原生的其实不是很好用,这里我推荐一个只需要引入一个js文件就能实现看板娘的方法,README在这里,这个作者对Live2d模型进行了深度优化,可以根据鼠标放置和点击内容的不同进行一定的提示信息,并且代码中设置了一年中特殊节日的祝福语,可以说是很有心了,唯一的缺点就是网页刷新时人物模型的加载速度较慢🌚~ 具体设置的话,我们去这个张书樵live2d-widget下载这个 zip 项目并解压到 themes/next/source 下。 然后打开下载文件中的 autoload.js 文件,修改以下代码: 1const live2d_path = "https://cdn.jsdelivr.net/gh/stevenjoezhang/live2d-widget/"; 为下面这个地址: 1const live2d_path = "/live2d-widget/"; 上面那个地址表示在本地引用,然后打开 themes/next/layout/_layout.swig 文件,在其中引入 js 文件如下: 1<script src="/live2d-widget/autoload.js"></script> 打开主题配置文件 themes/next/_config.xml 文件,在其中末尾添加: 12live2d: enable: true # 设置看板娘开关 最后,想修改看板娘大小、位置、格式、文本内容等,可查看并修改 waifu-tips.js 、 waifu-tips.json 和 waifu.css 三个文件。 彩色标签云 就是标签上增加随机颜色,每次刷新页面标签上展示颜色都不一样。打开 hexo/themes/next/layout/page.swig 文件,找到: 文件位置:hexo/themes/next/layout/page.swig 1{% if page.type === "tags" %} 然后将这段代码: 123456789<div class="tag-cloud"> <!-- <div class="tag-cloud-title"> {{ _p(counter.tag_cloud, site.tags.length) }} </div> --> <div class="tag-cloud-tags" id="tags"> {{ tagcloud({min_font: 16, max_font: 16, amount: 300, color: true, start_color: #fff, end_color: #fff}) }} </div> </div> 换成这段代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253<div class="tag-cloud"> <!-- <div class="tag-cloud-title"> {{ _p(counter.tag_cloud, site.tags.length) }} </div> --> <div class="tag-cloud-tags" id="tags"> {{ tagcloud({min_font: 16, max_font: 16, amount: 300, color: true, start_color: #fff, end_color: #fff}) }} </div></div><br><script type="text/javascript"> var alltags=document.getElementById(tags); var tags=alltags.getElementsByTagName(a); for (var i = tags.length - 1; i >= 0; i--) { var r=Math.floor(Math.random()*75+130); var g=Math.floor(Math.random()*75+100); var b=Math.floor(Math.random()*75+80); tags[i].style.background = "rgb("+r+","+g+","+b+")"; }</script><style type="text/css"> div#posts.posts-expand .tag-cloud a{ background-color: #f5f7f1; border-radius: 6px; padding-left: 10px; padding-right: 10px; margin-top: 18px; } .tag-cloud a{ background-color: #f5f7f1; border-radius: 4px; padding-right: 5px; padding-left: 5px; margin-right: 5px; margin-left: 0px; margin-top: 8px; margin-bottom: 0px; } .tag-cloud a:before{ content: "?"; } .tag-cloud-tags{ text-align: left; counter-reset: tags; }</style> 然后重新渲染页面就好啦~然后如果需要将标签云放到首页,直接在对应位置添加标签云的引用代码即可: 12345<div class="tag-cloud"> <div class="tag-cloud-tags" id="tags"> {{ tagcloud({min_font: 16, max_font: 16, amount: 300, color: true, start_color: '#fff', end_color: '#fff'}) }} </div></div> 鼠标样式 添加CSS样式代码: 文件位置:/themes/next/source/css/_custom/custom.styl 1234567/* 鼠标样式 */* { cursor: url(/images/default.cur),auto;}:link { cursor: url(/images/pointer.cur),auto} 用到的两个文件:default.cur、pointer.cur 位于 images 目录下,因为是 .cur 这种静态光标文件,编辑器打开是一堆 ASCII 码,这里就不贴了,直接附上链接,当然,你也可以在浏览器里获取。 https://hasaik.com/images/default.cur https://hasaik.com/images/pointer.cur 鼠标点击特效 鼠标点击常用4种特效可以参考的我的另外一篇博客。 Valine评论框样式美化 valine自带的样式比较素,并且颜色有点单调,不如花点时间将 valine 重新打造一下,注意我修改的 valine 样式只支持 1.3.4 版本的,其它版本的也可以修改,但是需要重新适配 CSS 样式。具体内容参考我的另外一篇博客。 归档页面美化 归档页面其实有很多大佬已经给出美化样式了,有的有翻页特效,比如像这样,或者直接简单点,可以参考我的另外一篇博客。 添加相册功能 这个实现的方式比较多,但是个人觉得比较实用好看的推荐参考这篇文章,相册展示图片样式可以在原作者基础上继续进行二次开发。我的个人相册是另一种方式哦,附上教程。 引入share.js分享功能 我目前使用的 Next5 自带的分享样式都不是很好看,百度分享虽然默认不支持 HTTPS,但是强行支持后总是在 console 控制台报错,我觉得挺烦的就直接删掉了。然后偶然在 Github 上看到一个 Share.js 感觉比较美观,就想办法引入到了个人博客。方法如下: 在这个 Share.js 中拷贝 dist 目录到本地的 hexo/themes/next/source 下。 在 hexo/themes/next/layout/_layout.swig 文件的 <head></head> 标签体内引入如下样式: 1<link rel="stylesheet" href="/dist/css/share.min.css"> 然后在下面 <body></body> 标签体内引入如下 js 文件: 1<script src="/dist/js/social-share.min.js"></script> 最后,在 hexo/themes/next/layout/post.swig 文件中添加如下代码: 12345678910111213141516 <div class="post-spread"> {% if theme.jiathis %} {% include '_partials/share/jiathis.swig' %} {% elseif theme.baidushare %} {% include '_partials/share/baidushare.swig' %} {% elseif theme.add_this_id %} {% include '_partials/share/add-this.swig' %} {% elseif theme.duoshuo_shortname and theme.duoshuo_share %} {% include '_partials/share/duoshuo_share.swig' %} <!-- 引入share.js -->+ {% elseif theme.share_js%}+ <div data-weibo-title="分享到微博" data-qq-title="分享到QQ" data-douban-title="分享到豆瓣" class="social-share" class="share-component" data-disabled="twitter,facebook" data-description="Share.js - 一键分享到微博,QQ空间,腾讯微博,人人,豆瓣">分享到:</div>+ {% endif %} </div> 并在主题配置文件中末尾添加如下: 文件位置:hexo/themes/next/_config.xml 1share_js: true 然后就可以使用了!里面具体一些属性设置可以查看原作者的README文档,介绍的很详细。 参考文章 造个性超赞博客 Hexo + NexT + GitHub Pages 的超深度优化 Hexo+Next主题优化 Hexo搭建个人博客–next主题优化 Hexo+Next个人博客主题优化 加速Hexo博客 hexo建站笔记之彩色标签云 hexo建站笔记之首页轮播图 原生js实现图片点击展示效果 在网页中添加live2d看板娘 Google Fonts已支持思源宋体 Hexo博客+Next主题深度优化与定制","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"}]},{"title":"Hexo+NexT 自定义样式博文加密","slug":"about-hexo-password","date":"2019-10-16T16:31:27.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/c1cdcf68.html","link":"","permalink":"https://blog.hasaik.com/posts/c1cdcf68.html","excerpt":"","text":"介绍一种自定义博文加密方式,不需要插件,极简模式,相对安全。 先看一下效果: 代码 1、在目录 /themes/next/layout/ 下的 _layout.swig 中,找到main标签添加自定义的 swig 123456789101112131415161718<main id="main" class="main"> <div class="main-inner"> <div class="content-wrap"> <div id="content" class="content"> {% block content %}{% endblock %} </div> {% include '_third-party/duoshuo-hot-articles.swig' %} {% include '_partials/comments.swig' %} </div> {% if theme.sidebar.display !== 'remove' %} {% block sidebar %}{% endblock %} {% endif %} </div> <!-- 此处为新加的代码 --> {% include 'password.swig' %}</main> 2、在目录 /themes/next/layout/ 下创建 password.swig ,内容如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283<script src="https://cdn.staticfile.org/jquery/3.1.1/jquery.min.js"></script><script> //暂时储存文章中的内容 var div = $('.post-body'); //暂时储存目录的内容 var toc=$('.post-toc') function password() { if('{{ page.password }}'){ //将文章内容删除 div.remove(); //将目录删除 toc.remove(); //将文章删除后,向原来文章的地方添加,应该出现的提示用户输入密码的样式 //下面这里的第一个用textarea是因为如果在手机端的时候只能显示一部分文字, //只是拓展:input里面的字只能显示一行,不会自动换行,目前上网搜索没有发现好的办法,所以用了textarea,右下角的小三角通过resize:none 去掉。 $('.post-header').after( '<span class="description" value="请输入密码,然后按 Enter 键阅读" style="font-style: oblique;font-weight: bold;border: none;display: block;'+ 'width: 60%;margin: 0 auto;text-align: center;outline: none;margin-bottom: 40px;resize:none ">'+ '<i class="fa fa-heartbeat" id="myheartbeat"></i>'+ '请输入密码,然后按 Enter 键阅读' + '<i class="fa fa-heartbeat" id="myheartbeat"></i>'+ '</span>' + '<div class="qiang" style="height: 100px;width: 60%;margin:0 auto">' + '<input class="password" type="password" autocomplete="new-password" autofocus="autofocus" value="" style="border-radius: 5px;height: 30px;border: none;display: block;border-bottom: 1px solid #ccc;' + 'margin: 0 auto;outline: none;width:95%"/>' + '</div>') //绑定点击事件,如果是点击的.password 这个div就改变样式,如果是document中除了div之外的其他任何元素,就变回原来的样式。 document.onclick = function (event) { var e = event || window.event; var elem = e.srcElement || e.target; while (elem) { if (elem != document) { if (elem.className == "password") { //$(".password").animate({paddingTop:"30px",width:"100%",borderWidth:"2px"},300) return; } elem = elem.parentNode; } else { //$(".password").animate({paddingTop:"0px",width:"95%",borderWidth:"1px"},300) return; } } } //绑定enter键按下后离开的事件 $(document).keyup(function(event){ if(event.keyCode ==13&&$('.password').length>0){ //console.log($('.password').val()) //console.log('{{ page.password }}') if ($('.password').val() == '{{ page.password }}') { //恢复文章内容 (div).appendTo($(".post-header")) //恢复目录 toc.appendTo($(".sidebar-inner")) //删除本页面的输入密码组件 $(".description").remove(); $(".qiang").remove(); $(".password").remove(); //重新处理pjax事件,如果没有加pjax的从下面这行起到下面的else之间的代码需要去掉。 //图片懒加载,没有加入此功能的这个函数需要去掉 $('img').lazyload({ placeholder: '/images/loading.gif', effect: 'fadeIn', threshold : 100, failure_limit : 20, skip_invisible : false }); //pjax后出现文章不显示,没有pjax的下面四行需要去掉 $(".post-block").css({opacity:1}); $(".post-header").css({opacity:1}); $(".post-body").css({opacity:1}); $(".pagination").css({opacity:1}); }else { alert("对不起,密码输入错误。") } } //将document的keyup移除,防止在pjax的情况下会重复绑定事件 }); } } password();</script> 使用 新建一个 test.md ,内容如下 12345678title: 测试date: 2019-03-30 21:18:02password: aaa---# aaaaaa我就很反感大家老是那么说我,## bbbbbb除了有才,就只剩下那无可比拟的颜值。 上面的 password 后面的值自定义。 注意 如果自己的博客源码中的这篇文章上传到 github ,密码也就公诸于世了,可以在 push 到 github 的时候将这篇文章忽略。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Password","slug":"Password","permalink":"https://blog.hasaik.com/tags/Password/"},{"name":"Security","slug":"Security","permalink":"https://blog.hasaik.com/tags/Security/"}]},{"title":"Hexo+NexT 博客搭建相册","slug":"about-hexo-photo","date":"2019-10-15T09:26:09.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/39d47c89.html","link":"","permalink":"https://blog.hasaik.com/posts/39d47c89.html","excerpt":"","text":"首先说一下实现想法,在 腾讯云开发者平台 上面创建一个相册库(PS:腾讯云开发者平台是腾讯云与 CODING 共同为开发者打造的云端工具平台,旨在为更多的开发者带去便捷、高效的开发体验,提供包括需求管理、代码编写、代码管理、代码运行的整套系统),当有更新时,提交到腾讯云开发者平台上面,同时在博客 resource 下面生成一个 data.json 来生成所有相册文件的 json 文件,博客读取 data.json 来展示相册。 实现效果链接: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Photoshttps://hasaik.com/photos/ 创建相册 在腾讯云开发者平台上面创建一个仓库,命名为 Blog_Back_Up (仓库名字随便). 用 git clone 把仓库 clone 到本地来. 1cd Blog_Back_Up 创建 photos 和 min_photos 两个目录,把要上传的相册图片 放到 photos 文件夹下面. ***相册图片命名方式 : yyyy-MM-dd_des.jpg/png/jpef/gif.  eg: 2017-9-18_风景.jpg*** 处理图片 图片的处理我用 python 脚本来处理,这样每次只要执行脚本就可以了。如果您的电脑没有 Python ,自行上网搜索安装教程,一搜一大把。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211#coding: utf-8from PIL import Imageimport osimport sysimport jsonfrom datetime import datetimefrom ImageProcess import Graphics# 定义压缩比,数值越大,压缩越小SIZE_normal = 1.0SIZE_small = 1.5SIZE_more_small = 2.0SIZE_more_small_small = 3.0def make_directory(directory): """创建目录""" os.makedirs(directory)def directory_exists(directory): """判断目录是否存在""" if os.path.exists(directory): return True else: return Falsedef list_img_file(directory): """列出目录下所有文件,并筛选出图片文件列表返回""" old_list = sorted(os.listdir(directory), reverse=True) print(old_list) new_list = [] for filename in old_list: name, fileformat = filename.split(".") if fileformat.lower() == "jpg" or fileformat.lower() == "png" or fileformat.lower() == "gif" or fileformat.lower() == "jpeg": new_list.append(filename) # print new_list return new_listdef print_help(): print(""" This program helps compress many image files you can choose which scale you want to compress your img(jpg/png/etc) 1) normal compress(4M to 1M around) 2) small compress(4M to 500K around) 3) smaller compress(4M to 300K around) """)def compress(choose, des_dir, src_dir, file_list): """压缩算法,img.thumbnail对图片进行压缩, 参数 ----------- choose: str 选择压缩的比例,有4个选项,越大压缩后的图片越小 """ if choose == '1': scale = SIZE_normal if choose == '2': scale = SIZE_small if choose == '3': scale = SIZE_more_small if choose == '4': scale = SIZE_more_small_small for infile in file_list: img = Image.open(src_dir+infile) # size_of_file = os.path.getsize(infile) w, h = img.size img.thumbnail((int(w/scale), int(h/scale))) img.save(des_dir + infile)def compress_photo(): '''调用压缩图片的函数 ''' src_dir, des_dir = "photos/", "min_photos/" if directory_exists(src_dir): if not directory_exists(src_dir): make_directory(src_dir) # business logic file_list_src = list_img_file(src_dir) if directory_exists(des_dir): if not directory_exists(des_dir): make_directory(des_dir) file_list_des = list_img_file(des_dir) # print file_list '''如果已经压缩了,就不再压缩''' for i in range(len(file_list_des)): if file_list_des[i] in file_list_src: file_list_src.remove(file_list_des[i]) compress('4', des_dir, src_dir, file_list_src)def handle_photo(): '''根据图片的文件名处理成需要的json格式的数据 ----------- 最后将data.json文件存到博客的source/photos文件夹下 ''' src_dir, des_dir = "photos/", "min_photos/" file_list = list_img_file(src_dir) print(file_list) list_info = [] for i in range(len(file_list)): filename = file_list[i] date_str, info = filename.split("_") info, _ = info.split(".") date = datetime.strptime(date_str, "%Y-%m-%d") year_month = date_str[0:7] if i == 0: # 处理第一个文件 new_dict = {"date": year_month, "arr":{'year': date.year, 'month': date.month, 'link': [filename], 'text': [info], 'type': ['image'] } } list_info.append(new_dict) elif year_month != list_info[-1]['date']: # 不是最后的一个日期,就新建一个dict new_dict = {"date": year_month, "arr":{'year': date.year, 'month': date.month, 'link': [filename], 'text': [info], 'type': ['image'] } } list_info.append(new_dict) else: # 同一个日期 list_info[-1]['arr']['link'].append(filename) list_info[-1]['arr']['text'].append(info) list_info[-1]['arr']['type'].append('image') list_info.reverse() # 翻转 tmp = bubbleYear(list_info) bubble(tmp) final_dict = {"list": list_info} with open("../xuxugood.github.io/source/photos/data.json","w") as fp: json.dump(final_dict, fp)def cut_photo(): """裁剪算法 ---------- 调用Graphics类中的裁剪算法,将src_dir目录下的文件进行裁剪(裁剪成正方形) """ src_dir = "photos/" if directory_exists(src_dir): if not directory_exists(src_dir): make_directory(src_dir) # business logic file_list = list_img_file(src_dir) # print file_list if file_list: print_help() for infile in file_list: img = Image.open(src_dir+infile) Graphics(infile=src_dir+infile, outfile=src_dir + infile).cut_by_ratio() else: pass else: print("source directory not exist!") def git_operation(): ''' git 命令行函数,将仓库提交 ---------- 需要安装git命令行工具,并且添加到环境变量中 ''' os.system('git add --all') os.system('git commit -m "add photos"') os.system('git push origin master')def bubble(bubbleList): listLength = len(bubbleList) while listLength > 0: for i in range(listLength - 1): # 这个循环负责设置冒泡排序进行的次数 # print(bubbleList[i]) for j in range(listLength-i-1): # j为列表下标 if(bubbleList[j].get('arr').get('year') == bubbleList[j+1].get('arr').get('year')): if bubbleList[j].get('arr').get('month') < bubbleList[j+1].get('arr').get('month'): bubbleList[j], bubbleList[j+1] = bubbleList[j+1], bubbleList[j] return bubbleList # for i in range(listLength - 1): # if(bubbleList[i].get('arr').get('year') == bubbleList[i+1].get('arr').get('year')): # if bubbleList[i].get('arr').get('month') > bubbleList[i+1].get('arr').get('month'): # bubbleList[i] = bubbleList[i] + bubbleList[i+1] # bubbleList[i+1] = bubbleList[i] - bubbleList[i+1] # bubbleList[i] = bubbleList[i] - bubbleList[i+1] # listLength -= 1 def bubbleYear(bubbleList): listLength = len(bubbleList) while listLength > 0: for i in range(listLength - 1): for j in range(listLength-i-1): if bubbleList[j].get('arr').get('year') < bubbleList[j+1].get('arr').get('year'): bubbleList[j], bubbleList[j+1] = bubbleList[j+1], bubbleList[j] # print(bubbleList) return bubbleListif __name__ == "__main__": cut_photo() # 裁剪图片,裁剪成正方形,去中间部分 compress_photo() # 压缩图片,并保存到mini_photos文件夹下 git_operation() # 提交到github仓库 handle_photo() # 将文件处理成json格式,存到博客仓库中 其中 ../xuxugood.github.io/source/photos/data.json 是我博客地址,这里换成你的博客地址。 使用方法 执行命令 python3 tool.py ,因为我用的是 python3 这里可以根据你的 python 版本来使用。 问题 如果出现 from PIL import Image 这种报错,说明没有 PIL 这个库,执行 python3 -m pip install Pillow 增加相册style 在 Next 主题下面增加 photo.swig 页面,路径如下 next/layout 1234567891011121314151617181920212223242526272829{% extends '_layout.swig' %}{% import '_macro/post-collapse.swig' as post_template %}{% import '_macro/sidebar.swig' as sidebar_template %}{% block title %}{{ page.title }} | {{ config.title }}{% endblock %}{% block content %} {#################} {### Photo BLOCK ###} {#################} <div class="post-block photo"> <div id="posts" class="posts-collapse"> </div> </div> {#####################} {### END Photo BLOCK ###} {#####################} {% include '_partials/pagination.swig' %}{% endblock %}{% block sidebar %} {{ sidebar_template.render(false) }}{% endblock %} 生成相册页面 生成相册页面 hexo new page photos,修改 photos 下的 index.md 文件如下 123456789101112131415161718192021222324252627title: 我的相册date: 2017-09-15 09:51:05type: "photos"comments: false---<link rel="stylesheet" href="./ins.css"> <link rel="stylesheet" href="./photoswipe.css"> <link rel="stylesheet" href="./default-skin/default-skin.css"> <div class="photos-btn-wrap"> <a class="photos-btn active" href="javascript:void(0)">Photos</a></div><div class="instagram itemscope"> <a href="https://xuxugood.github.io" target="_blank" class="open-ins">图片正在加载中…</a></div> <script> (function() { var loadScript = function(path) { var $script = document.createElement('script') document.getElementsByTagName('body')[0].appendChild($script) $script.setAttribute('src', path) } setTimeout(function() { loadScript('./ins.js') }, 0) })()</script> 其中 <a href="https://xuxugood.github.io" target="_blank" class="open-ins">图片正在加载中…</a> 中的 url 替换成你的博客网址。 需要三个 css 文件和一个 js 文件放在 photos 文件夹下,其文件都在我的 腾讯云开发者平台 上面,需要修改 ins.js 的 120 和 121 行的 url 为你腾讯云开发者平台图片的网址。 查看相册插件 photoswipe 上面 index.md 中加入了两个 css 文件,这是我们用 photoswipe 查看相册用到的,具体可以参考网址 photoswipe 。这里我们已经把 css 文件加上了,之后我们要加上 js 文件 photoswipe.min.js 和 photoswipe-ui-default.min.js,js 资源下载地址 photoswipe ,js 存放路径为 next/source/js/src 引用 js 文件 在 layout/_scripts/pages/post-details.swig 中插入 12<script src="{{ url_for(theme.js) }}/src/photoswipe.min.js?v={{ theme.version }}"></script><script src="{{ url_for(theme.js) }}/src/photoswipe-ui-default.min.js?v={{ theme.version }}"></script> 在根目录加入标签 在 _layout.swig 中的body标签里最前面插入以下内容 1234567891011121314151617181920212223242526272829303132333435363738394041{% if page.type === "photos" %}<!-- Root element of PhotoSwipe. Must have class pswp. --><div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"> <div class="pswp__bg"></div> <div class="pswp__scroll-wrap"> <div class="pswp__container"> <div class="pswp__item"></div> <div class="pswp__item"></div> <div class="pswp__item"></div> </div> <div class="pswp__ui pswp__ui--hidden"> <div class="pswp__top-bar"> <div class="pswp__counter"></div> <button class="pswp__button pswp__button--close" title="Close (Esc)"></button> <button class="pswp__button pswp__button--share" title="Share"></button> <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button> <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button> <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR --> <!-- element will get class pswp__preloader--active when preloader is running --> <div class="pswp__preloader"> <div class="pswp__preloader__icn"> <div class="pswp__preloader__cut"> <div class="pswp__preloader__donut"></div> </div> </div> </div> </div> <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"> <div class="pswp__share-tooltip"></div> </div> <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"> </button> <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"> </button> <div class="pswp__caption"> <div class="pswp__caption__center"></div> </div> </div> </div></div>{% endif %} 至此相册查看插件 photoswipe 已经配置完毕。 整个流程使用 在 Blog_Back_Up 里面加入图片,图片路径在 photos 里面 图片命名方式 yyyy-MM-dd_des.jpg/jpeg/gif/png 执行 python3 tool.py 切换到博客 resource 目录下 在 photos 里面生成了 data.json 文件,提交到腾讯云开发者平台仓库上面 输入网址查看照片","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Photos","slug":"Photos","permalink":"https://blog.hasaik.com/tags/Photos/"}]},{"title":"Hexo NexT主题之代码块Mac Panel特效","slug":"about-hexo4","date":"2019-10-14T14:45:29.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/d7399e80.html","link":"","permalink":"https://blog.hasaik.com/posts/d7399e80.html","excerpt":"","text":"偶然间发现一款不错的文章代码块样式,类似Mac的面板效果。能设置阴影效果和实现文本编辑功能,不过文本只存在浏览器页面上,不会真正保存。配置的方式也很简单,觉得不错的朋友可以试一下。 引入 JS 这里需要新建两个 js 文件 events.js 和 codeblock.js ,路径位于 /themes/next/scripts/ 包下。 events.js 代码: 1234567// mac Panel效果代码块相关var exec = require('child_process').exec;// new 后自动打开编辑器hexo.on('new', function(data){ exec('open -a MacDown ' + data.path);}); 这个js会在你敲 hexo new xxx 命令后,调用本地的MarkDown编辑器打开新建的md文件 xxx codeblock.js 代码: 12345678910111213141516171819202122// mac Panel效果代码块相关var attributes = [ 'autocomplete="off"', 'autocorrect="off"', 'autocapitalize="off"', 'spellcheck="false"', 'contenteditable="true"']var attributesStr = attributes.join(' ')hexo.extend.filter.register('after_post_render', function (data) { while (/<figure class="highlight ([a-zA-Z]+)">.*?<\\/figure>/.test(data.content)) { data.content = data.content.replace(/<figure class="highlight ([a-zA-Z]+)">.*?<\\/figure>/, function () { var language = RegExp.$1 || 'plain' var lastMatch = RegExp.lastMatch lastMatch = lastMatch.replace(/<figure class="highlight /, '<figure class="iseeu highlight /') return '<div class="highlight-wrap"' + attributesStr + 'data-rel="' + language.toUpperCase() + '">' + lastMatch + '</div>' }) } return data}) 引入 CSS 在 /themes/next/source/css/_common/components/highlight/ 目录下新建 macPanel.styl 文件,内容如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758/*mac Panel效果代码块相关*/.highlight-wrap[data-rel] { position: relative; overflow: hidden; border-radius: 5px; /*box-shadow: 0 10px 30px 0px rgba(0, 0, 0, 0.4);*/ box-shadow:18px 18px 15px 0px rgba(0,0,0,.4); margin: 35px 0; margin-top: 10px; margin-bottom: 25px; ::-webkit-scrollbar { height: 10px; } ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); border-radius: 10px; } ::-webkit-scrollbar-thumb { border-radius: 10px; -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); } &::before { content: attr(data-rel); height: 38px; line-height: 38px; background: #21252b; /*background: #108414de;*/ color: #fff; font-size: 16px; /*position: absolute;*/ top: 0; left: 0; width: 100%; /*font-family: 'Source Sans Pro', sans-serif;*/ font-weight: bold; padding: 0px 80px; text-indent: 15px; float: left; } &::after { content: ' '; position: absolute; -webkit-border-radius: 50%; border-radius: 50%; background: #fc625d; width: 12px; height: 12px; top: 0; left: 20px; margin-top: 13px; -webkit-box-shadow: 20px 0px #fdbc40, 40px 0px #35cd4b; box-shadow: 20px 0px #fdbc40, 40px 0px #35cd4b; z-index: 3; }} 此css是根据我本地的样式做过调整,注释的代码为原有的,根据需要调整样式即可。 配置引用 在 /themes/next/source/css/_common/components/highlight/highlight.styl 中引入刚才新建的 macPanel.styl: 1@require "macPanel" 配置在文件的顶部位置即可。 到此Mac Panel配置完成,根据需要可调整主题配置文件中的 highlight_theme 的值,选择自己喜欢的样式。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"代码块","slug":"代码块","permalink":"https://blog.hasaik.com/tags/%E4%BB%A3%E7%A0%81%E5%9D%97/"}]},{"title":"Hexo博客静态资源压缩","slug":"about-hexo3","date":"2019-10-13T12:52:32.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/495d0b23.html","link":"","permalink":"https://blog.hasaik.com/posts/495d0b23.html","excerpt":"","text":"针对于博文静态资源压缩,介绍一下两种压缩方式,第一种方式是使用Gulp来进行压缩,Gulp 是 Node.js 下的自动构建工具,通过一列的task执行步骤进行自动流程化处理。第二种方式就是使用由rozbo大佬开发的 Hexo-Neat 压缩插件,配置简单,无需额外命令。 附上大佬的 Github 链接: .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}Hexo-Neathttps://github.com/rozbo/hexo-neat Hexo-Neat使用 1、在站点根目录下安装 Hexo-Neat 1$ npm install hexo-neat --save 2、在站点配置文件中末尾添加以下相关配置即可,也可以按照自己的需求去自定义配置。 12345678910111213141516171819202122# hexo-neat# 博文压缩neat_enable: true# 压缩htmlneat_html: enable: true exclude:# 压缩cssneat_css: enable: true exclude: - '*/*.min.css'# 压缩jsneat_js: enable: true mangle: true output: compress: exclude: - '**/*.min.js' - '**/jquery.fancybox.pack.js' - '**/index.js' Gulp使用 1、在站点的根目录下执行以下命令 12$ npm install gulp -g$ npm install gulp-minify-css gulp-uglify gulp-htmlmin gulp-htmlclean gulp --save 2、在博客根目录下新建 gulpfile.js ,并填入以下内容 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051var gulp = require('gulp');var minifycss = require('gulp-minify-css');var uglify = require('gulp-uglify');var htmlmin = require('gulp-htmlmin');var htmlclean = require('gulp-htmlclean');var imagemin = require('gulp-imagemin');// 压缩htmlgulp.task('minify-html', function() { return gulp.src('./public/**/*.html') .pipe(htmlclean()) .pipe(htmlmin({ collapseWhitespace: true, //从字面意思应该可以看出来,清除空格,压缩html,这一条比较重要,作用比较大,引起的改变压缩量也特别大 collapseBooleanAttributes: true, //省略布尔属性的值,比如:<input checked="checked"/>,那么设置这个属性后,就会变成 <input checked/> removeComments: true, //清除html中注释的部分 removeEmptyAttributes: true, //清除所有的空属性 removeScriptTypeAttributes: true, //清除所有script标签中的type="text/javascript"属性。 removeStyleLinkTypeAttributes: true, //清楚所有Link标签上的type属性。 minifyJS: true, minifyCSS: true, minifyURLs: true, })) .pipe(gulp.dest('./public'));});// 压缩cssgulp.task('minify-css', function() { return gulp.src('./public/**/*.css') .pipe(minifycss({ compatibility: 'ie8' })) .pipe(gulp.dest('./public'));});// 压缩js !代表排除的js,例如['!./public/js/**/*min.js']gulp.task('minify-js', function() { return gulp.src(['./public/js/**/.js']) .pipe(uglify()) //压缩混淆 .pipe(gulp.dest('./public'));});// 压缩图片gulp.task('minify-images', function() { return gulp.src('./public/images/**/*.*') .pipe(imagemin( [imagemin.gifsicle({'optimizationLevel': 3}), imagemin.jpegtran({'progressive': true}), imagemin.optipng({'optimizationLevel': 7}), imagemin.svgo()], {'verbose': true})) .pipe(gulp.dest('./public/images'));});// 默认任务gulp.task('default',gulp.series(gulp.parallel('minify-html','minify-css','minify-js','minify-images'))); 3、生成博文时执行 hexo g && gulp 就会根据 gulpfile.js 中的配置,对 public 目录中的静态资源文件进行压缩。 以上就是关于博文静态资源压缩的两种方式,欢迎自由选择引用,如有不明白的地方欢迎下方留言 o(^▽^)o ,谢谢阅读。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Gulp","slug":"Gulp","permalink":"https://blog.hasaik.com/tags/Gulp/"},{"name":"Neat","slug":"Neat","permalink":"https://blog.hasaik.com/tags/Neat/"}]},{"title":"基于 TravisCI 实现 Hexo 在 Github 和 Coding 的同步部署","slug":"about-hexo2","date":"2019-10-12T14:43:34.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/3e5a3bb6.html","link":"","permalink":"https://blog.hasaik.com/posts/3e5a3bb6.html","excerpt":"","text":"关于TravisCI我这里简单介绍一下,TravisCI是一个在线的、分布式的持续集成服务,可以用来构建和测试托管在Github上的代码,并且其本身就是开源的。TravisCI提供了主流编程语言如C#、Java、JavaScript、Ruby、PHP、Node.js等的支持,相比Jenkins而言,它是一个轻量级的持续集成平台,它会在每次提交代码后,根据配置文件来创建一个虚拟机,并执行用户定义的Build任务,这个虚拟机提供版本控制(Git)、项目构建(Node.js)等,在此前提下,我们下面着手Hexo的自动化部署。 其原理就是Github和Coding(小插曲:Coding现在已经被腾讯收购了)各为TravisCI分配一个token,当我们向 Github 推送新的代码以后,TravisCI就会从代码仓库中拉取代码,并通过 npm 安装依赖生成静态页面,我们将这些静态页面推送到 master 分支,即可完成对Hexo的部署操作。 对于我个人博客是以 Github 作为代码的主仓库,其上面的 blog-source 分支存放博客的源代码,master 分支存放博客的静态页面,在此基础上,我们同时推送静态页面到 Github 和 Coding 的代码仓库,这样就可以实现两个平台的同步部署,这里的部署自然是指由 Travis 完成的自动化部署。整体的流程如下图所示: Github相关操作 1、按规定仓库名称为 XXXXX.github.io,其中 XXXXX 为你的用户名,进行创建仓库。 2、建好仓库以后我们在仓库中新建一个分支放博客源码,我这里命名为 blog-source,建好以后将源码提交到该分支下即可。 3、为了使 Travis 能够将编译好的文件 push 回咱们的 Github,我们需要生成 token,步骤如下: 在 Github 上 Setting 中找到 Personal access tokens 设置。 点 Generate new token,为 token 起一个名字,勾选 repo,然后点击生成一个新的 token ,并复制下来记录好,待会下面 Travis 配置会用到。(这个只会出现一次!!) Coding相关操作 1、因为腾讯云已经收购了 Coding ,所以我们直接在 腾讯云开发者平台 注册账号来管理我们的仓库。 2、注册完毕后我们新建一个名为 XXXXX.coding.me 的项目,其中 XXXXX 为你的用户名,基本操作与 Github 一致,实在不会的可以留言。 3、Coding可以和 Github 一样可以生成一个 token 如下图,成功以后将 token 保存好,一会下面会用到。 Travis相关操作 1、使用 github 帐号登录 TravisCI ,左上方按钮点击同步项目,下方打开需要集成的项目,最后点击齿轮进入项目配置页面 2、具体 Travis 配置如下图 3、配置好 Travis 后,回到终端,进入 blog 所在的文件夹下,新建 .travis.yml 文件,并添加以下内容 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364sudo: falselanguage: node_jsnode_js: - 10 # use nodejs v10 LTS# 指定缓存模块,可选。缓存可加快编译速度。cache: directories: - node_modules# 指定博客的仓库地址env: global: # Github Pages - GH_REF: github.com/XuxuGood/XuxuGood.github.io # Coding Pages - CO_REF: git.dev.tencent.com/XuxuGood/XuxuGood.coding.me.git# 指定博客分支branches: only: - blog-source # build master branch onlybefore_install: - npm install -g hexo-cli# Start: Build Lifecycleinstall: - npm install - npm install hexo-deployer-git --save# 执行清缓存,生成网页操作script: - hexo clean - hexo generate #gulp压缩文件 - gulp# 设置git提交名,邮箱;替换真实token到_config.yml文件,最后depoy部署after_script: - cd ./public - git init - git config user.name "xuxu" - git config user.email "[email protected]" - git add . - git commit -m "TravisCI 自动部署" # Github Pages - git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master # Coding Pages - git push --force --quiet "https://XuxuGood:${CO_TOKEN}@${CO_REF}" master:master - git tag v0.0.$TRAVIS_BUILD_NUMBER -a -m "Auto Taged By TravisCI With Build $TRAVIS_BUILD_NUMBER" # Github Pages - git push --quiet "https://${GH_TOKEN}@${GH_REF}" master:master --tags # Coding Pages - git push --quiet "https://XuxuGood:${CO_TOKEN}@${CO_REF}" master:master --tags # Build Salver Repository(Github Pages) #- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master # Build Salver Repository(Github Pages) #- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master# End: Build LifeCycle 4、然后,准备 push 该项目到 github ,看下是否成功,最终成功则会看到 Travis 构建页面显示如下图 以上就是基于 TravisCI 实现 Hexo 在 Github 和 Coding 同步部署的全部介绍,如有不明白的地方欢迎下方留言 o(▽)o ,谢谢阅读。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Github","slug":"Github","permalink":"https://blog.hasaik.com/tags/Github/"},{"name":"Coding","slug":"Coding","permalink":"https://blog.hasaik.com/tags/Coding/"},{"name":"TravisCI","slug":"TravisCI","permalink":"https://blog.hasaik.com/tags/TravisCI/"}]},{"title":"Hexo搭建本地个人博客(基础篇)","slug":"about-hexo1","date":"2019-10-12T09:21:07.000Z","updated":"2021-03-25T13:28:10.065Z","comments":true,"path":"posts/e1b9c6c5.html","link":"","permalink":"https://blog.hasaik.com/posts/e1b9c6c5.html","excerpt":"","text":"Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 安装Hexo 建立 Hexo 只需要几分钟,安装 Hexo 非常简单。但是,您首先需要安装其他一些东西。 Node.js (Node.js 版本需不低于 8.6,建议使用 Node.js 10.0 及以上版本) Git 如果您的电脑中已经安装上述必备程序,那么恭喜您!接下来只需要使用 npm 即可完成 Hexo 的安装。 1$ npm install -g hexo-cli 如果没有,那就一起来看下面吧 (这里只说一下 windows 的安装,因为本人现在比较穷,买不起mac啊 テ_デ,其他相关教程大家就去搜搜很多的。) 安装git .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}安装地址https://git-scm.com/downloads 一切按照默认走就行了,没什么特殊的地方,安装完成之后检查git是否安装成功(执行一下cmd命令),显示版本号即为成功! 1git --version 安装Node.js .LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard{position:relative;display:block;margin:5px auto;width:330px;box-sizing:border-box;border-radius:12px;max-width:100%;overflow:hidden;color:inherit;text-decoration:none}.ztext{word-break:break-word;line-height:1.6}.LinkCard-backdrop{position:absolute;top:0;left:0;right:0;bottom:0;background-repeat:no-repeat;-webkit-filter:blur(20px);filter:blur(20px);background-size:cover;background-position:center}.LinkCard,.LinkCard:hover{text-decoration:none;border:none!important;color:inherit!important}.LinkCard-content{position:relative;display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:inherit;background-color:rgba(246,246,246,0.88)}.LinkCard-text{overflow:hidden;width:260px;}.LinkCard-title{white-space: nowrap;display:-webkit-box;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;max-height:calc(16px * 1.25 * 2);font-size:16px;font-weight:500;line-height:1.25;color:#1a1a1a}@media(max-width: 767px){.LinkCard-title{font-size:13px;}}.LinkCard-meta{display:flex;margin-top:4px;font-size:14px;line-height:20px;color:#999;white-space:nowrap}.LinkCard-imageCell{margin-left:28px;border-radius:30px;width:70px;}.LinkCard-image{background-image:url('https://cdn.jsdelivr.net/gh/XuxuGood/cdn@master/blogImages/linkcard/spider-man.png');background-size: 100% 100%;display:block;width:65px;height:65px;border-radius:inherit;margin-bottom: 0 !important;}安装地址https://nodejs.org/en/ 我们这里简单点,直接下载并运行安装程序就完了,还是走默认就行,安装完成之后检查 node 是否安装成功(执行一下cmd命令),显示版本号即为成功! 1node -v 安装Hexo 所有必备的应用程序安装完成后,即可使用 npm 安装 Hexo。 1$ npm install -g hexo-cli 利用Hexo初始化我们的站点跟目录(文件) 123$ hexo init <文件夹>$ cd <文件夹>$ npm install 选择你想要的盘符来建立我们的博客站点文件,我这里选择 D:\\blog ,这里的 blog 是你的文件夹名字(根据自己的喜好建一个文件夹) cd 到你的站点目录下,然后 初始化站点 $ npm install,执行成功后到你的 blog 文件夹下看看是否这样的(一致就成功啦~~) 介绍几个个命令 ,以后经常要用到的 123hexo g: 编译,生成静态文件,也就是public文件夹的东西。hexo s: 开启本地服务(以上两步的操作可以合并成hexo s -g)。hexo clean: 顾名思义就是清除缓存的意思了啦,这招一般在你改动之后网站没有变化时候用。 接下来看看 你博客的初步成果吧。 进入 blog 文件根目录: 执行命令: hexo g 和 hexo s 然后在你的浏览器输入http://localhost:4000,查看你的博客。 到此为止,你的个人博客就已经搭建完成了。","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"}]}],"categories":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/categories/Java/"},{"name":"线程","slug":"Java/线程","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/"},{"name":"Shell","slug":"Java/线程/Shell","permalink":"https://blog.hasaik.com/categories/Java/%E7%BA%BF%E7%A8%8B/Shell/"},{"name":"工具类","slug":"Java/工具类","permalink":"https://blog.hasaik.com/categories/Java/%E5%B7%A5%E5%85%B7%E7%B1%BB/"},{"name":"优化","slug":"Java/优化","permalink":"https://blog.hasaik.com/categories/Java/%E4%BC%98%E5%8C%96/"},{"name":"自动部署","slug":"自动部署","permalink":"https://blog.hasaik.com/categories/%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2/"},{"name":"JVM","slug":"Java/JVM","permalink":"https://blog.hasaik.com/categories/Java/JVM/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/categories/Vue/"},{"name":"MySql","slug":"MySql","permalink":"https://blog.hasaik.com/categories/MySql/"},{"name":"事务","slug":"MySql/事务","permalink":"https://blog.hasaik.com/categories/MySql/%E4%BA%8B%E5%8A%A1/"},{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/categories/SpringBoot/"},{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/categories/Hexo/"},{"name":"Gitlab","slug":"Gitlab","permalink":"https://blog.hasaik.com/categories/Gitlab/"},{"name":"流媒体服务","slug":"流媒体服务","permalink":"https://blog.hasaik.com/categories/%E6%B5%81%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1/"},{"name":"BUG","slug":"Java/BUG","permalink":"https://blog.hasaik.com/categories/Java/BUG/"},{"name":"加密安全","slug":"Java/加密安全","permalink":"https://blog.hasaik.com/categories/Java/%E5%8A%A0%E5%AF%86%E5%AE%89%E5%85%A8/"}],"tags":[{"name":"Java","slug":"Java","permalink":"https://blog.hasaik.com/tags/Java/"},{"name":"线程","slug":"线程","permalink":"https://blog.hasaik.com/tags/%E7%BA%BF%E7%A8%8B/"},{"name":"Shell","slug":"Shell","permalink":"https://blog.hasaik.com/tags/Shell/"},{"name":"工具类","slug":"工具类","permalink":"https://blog.hasaik.com/tags/%E5%B7%A5%E5%85%B7%E7%B1%BB/"},{"name":"优化","slug":"优化","permalink":"https://blog.hasaik.com/tags/%E4%BC%98%E5%8C%96/"},{"name":"Gitlab","slug":"Gitlab","permalink":"https://blog.hasaik.com/tags/Gitlab/"},{"name":"Jenkins","slug":"Jenkins","permalink":"https://blog.hasaik.com/tags/Jenkins/"},{"name":"Docker","slug":"Docker","permalink":"https://blog.hasaik.com/tags/Docker/"},{"name":"JVM","slug":"JVM","permalink":"https://blog.hasaik.com/tags/JVM/"},{"name":"通信","slug":"通信","permalink":"https://blog.hasaik.com/tags/%E9%80%9A%E4%BF%A1/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.hasaik.com/tags/Vue/"},{"name":"文件上传","slug":"文件上传","permalink":"https://blog.hasaik.com/tags/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"},{"name":"事务","slug":"事务","permalink":"https://blog.hasaik.com/tags/%E4%BA%8B%E5%8A%A1/"},{"name":"SpringBoot","slug":"SpringBoot","permalink":"https://blog.hasaik.com/tags/SpringBoot/"},{"name":"Allatori","slug":"Allatori","permalink":"https://blog.hasaik.com/tags/Allatori/"},{"name":"Hexo","slug":"Hexo","permalink":"https://blog.hasaik.com/tags/Hexo/"},{"name":"Folding容器","slug":"Folding容器","permalink":"https://blog.hasaik.com/tags/Folding%E5%AE%B9%E5%99%A8/"},{"name":"CentOS","slug":"CentOS","permalink":"https://blog.hasaik.com/tags/CentOS/"},{"name":"LocalDateTime","slug":"LocalDateTime","permalink":"https://blog.hasaik.com/tags/LocalDateTime/"},{"name":"Emoji","slug":"Emoji","permalink":"https://blog.hasaik.com/tags/Emoji/"},{"name":"nginx-http-flv-module","slug":"nginx-http-flv-module","permalink":"https://blog.hasaik.com/tags/nginx-http-flv-module/"},{"name":"视频流播放","slug":"视频流播放","permalink":"https://blog.hasaik.com/tags/%E8%A7%86%E9%A2%91%E6%B5%81%E6%92%AD%E6%94%BE/"},{"name":"Jwt","slug":"Jwt","permalink":"https://blog.hasaik.com/tags/Jwt/"},{"name":"AOP","slug":"AOP","permalink":"https://blog.hasaik.com/tags/AOP/"},{"name":"BUG","slug":"BUG","permalink":"https://blog.hasaik.com/tags/BUG/"},{"name":"el-date-picker","slug":"el-date-picker","permalink":"https://blog.hasaik.com/tags/el-date-picker/"},{"name":"Axios","slug":"Axios","permalink":"https://blog.hasaik.com/tags/Axios/"},{"name":"Rss","slug":"Rss","permalink":"https://blog.hasaik.com/tags/Rss/"},{"name":"SpringBoot打包","slug":"SpringBoot打包","permalink":"https://blog.hasaik.com/tags/SpringBoot%E6%89%93%E5%8C%85/"},{"name":"邮件订阅","slug":"邮件订阅","permalink":"https://blog.hasaik.com/tags/%E9%82%AE%E4%BB%B6%E8%AE%A2%E9%98%85/"},{"name":"lazyload","slug":"lazyload","permalink":"https://blog.hasaik.com/tags/lazyload/"},{"name":"图片","slug":"图片","permalink":"https://blog.hasaik.com/tags/%E5%9B%BE%E7%89%87/"},{"name":"Base64","slug":"Base64","permalink":"https://blog.hasaik.com/tags/Base64/"},{"name":"MD5","slug":"MD5","permalink":"https://blog.hasaik.com/tags/MD5/"},{"name":"SHA-1","slug":"SHA-1","permalink":"https://blog.hasaik.com/tags/SHA-1/"},{"name":"Valine","slug":"Valine","permalink":"https://blog.hasaik.com/tags/Valine/"},{"name":"点击特效","slug":"点击特效","permalink":"https://blog.hasaik.com/tags/%E7%82%B9%E5%87%BB%E7%89%B9%E6%95%88/"},{"name":"豆瓣","slug":"豆瓣","permalink":"https://blog.hasaik.com/tags/%E8%B1%86%E7%93%A3/"},{"name":"Password","slug":"Password","permalink":"https://blog.hasaik.com/tags/Password/"},{"name":"Security","slug":"Security","permalink":"https://blog.hasaik.com/tags/Security/"},{"name":"Photos","slug":"Photos","permalink":"https://blog.hasaik.com/tags/Photos/"},{"name":"代码块","slug":"代码块","permalink":"https://blog.hasaik.com/tags/%E4%BB%A3%E7%A0%81%E5%9D%97/"},{"name":"Gulp","slug":"Gulp","permalink":"https://blog.hasaik.com/tags/Gulp/"},{"name":"Neat","slug":"Neat","permalink":"https://blog.hasaik.com/tags/Neat/"},{"name":"Github","slug":"Github","permalink":"https://blog.hasaik.com/tags/Github/"},{"name":"Coding","slug":"Coding","permalink":"https://blog.hasaik.com/tags/Coding/"},{"name":"TravisCI","slug":"TravisCI","permalink":"https://blog.hasaik.com/tags/TravisCI/"}]}