diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..16b021ceb --- /dev/null +++ b/404.html @@ -0,0 +1,2 @@ +404 Page not found | 流动 +

404, page not found.

\ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..36e4d2acf --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +liudon.com diff --git a/_headers b/_headers new file mode 100644 index 000000000..14823c5b2 --- /dev/null +++ b/_headers @@ -0,0 +1,5 @@ +/* + strict-transport-security: max-age=31536000; includeSubDomains; preload + +https://liudon-github-io.pages.dev/* + X-Robots-Tag: noindex diff --git a/about/index.html b/about/index.html new file mode 100644 index 000000000..d106d57ab --- /dev/null +++ b/about/index.html @@ -0,0 +1,14 @@ +关于本站 | 流动 +

关于本站

大家好,欢迎访问我的博客。

建站缘由

大学期间,被人忽悠报了一个计算机培训班,误打误撞的进入了互联网行业。

11年在网友的帮助下,用wordPress搭建了博客,随即开始了我的博主生涯。

最开始的域名是liudon.org,取自本人名字的拼音,14年的时候经历了一次域名被盗,好在最后找回了。

中间又申请了liudon.xyz域名,一直垂涎于liudon.com,终于在22年被我拿到手了,十一年的等待

本站使用hugo生成,存储在Github上,通过Cloudflare PagesIPFS部署访问。

关于本人

喜欢做梦、已婚的80后男生。

如果您有意见或建议,欢迎您通过i@liudon.org联系我。

朋友们

Dvel’s Blog
Less is More.
林木木木木木
木木木木木
涛叔
涛叔
老张博客
老张博客
  • 文章作者:
  • 文章链接:https://liudon.com/about/
  • 版权声明:本站采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 https://liudon.com
💬评论
\ No newline at end of file diff --git a/ads.txt b/ads.txt new file mode 100644 index 000000000..ff4621d79 --- /dev/null +++ b/ads.txt @@ -0,0 +1 @@ +google.com, pub-4739645989170648, DIRECT, f08c47fec0942fa0 diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 000000000..4c5ecea3d --- /dev/null +++ b/archives/index.html @@ -0,0 +1,39 @@ +归档 | 流动 +

2024 + 15

September + 6

Github Pages 部署流程解析

2024-09-24 · 2 min · 570 words · Liudon

搭建个人锻炼页面

2024-09-22 · 1 min · 325 words · Liudon

你好 Follow

2024-09-17 · 1 min · 271 words · Liudon

中秋爬山

2024-09-16 · 2 min · 704 words · Liudon

Google Adsense的审核之旅

2024-09-16 · 1 min · 411 words · Liudon

让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

2024-09-04 · 3 min · 1246 words · Liudon

August + 1

一次简短的青岛之行

2024-08-31 · 2 min · 998 words · Liudon

June + 1

解决 “undeclared name: any (requires version go1.18 or later)” 编译错误

2024-06-14 · 1 min · 473 words · Liudon

May + 2

搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

2024-05-22 · 3 min · 1262 words · Liudon

302跳转的跨域问题(CORS)

2024-05-17 · 2 min · 721 words · Liudon

April + 2

GORM增加sqlcommenter特性

2024-04-18 · 2 min · 742 words · Liudon

源码分析:GORM是如何生成sql的

2024-04-18 · 6 min · 2737 words · Liudon

March + 1

工银亚洲网银密码重置

2024-03-16 · 1 min · 481 words · Liudon

February + 1

加速Cloudflare访问

2024-02-21 · 3 min · 1128 words · Liudon

January + 1

2023年终总结

2024-01-04 · 2 min · 748 words · Liudon

2023 + 15

December + 3

2023年12月北京暴雪记录

2023-12-16 · 2 min · 599 words · Liudon

使用Hugo实现响应式和优化的图片

2023-12-10 · 5 min · 2021 words · Liudon

加速Google Analytics

2023-12-02 · 2 min · 870 words · Liudon

October + 3

使用Google Indexing API加速博客收录

2023-10-27 · 2 min · 635 words · Liudon

在Netlify上部署Twikoo评论系统

2023-10-19 · 5 min · 2232 words · Liudon

利用Github Actions定时抓取微博

2023-10-07 · 2 min · 823 words · Liudon

September + 2

北大口腔牙周刮治记录

2023-09-17 · 2 min · 716 words · Liudon

故乡回忆之旅

2023-09-09 · 1 min · 292 words · Liudon

August + 1

解决Golang使用go get安装包后找不到可执行文件的问题

2023-08-17 · 1 min · 195 words · Liudon

March + 2

修正Hugo的JSON Feed格式

2023-03-25 · 3 min · 1451 words · Liudon

我的学车之路

2023-03-24 · 1 min · 375 words · Liudon

February + 2

将博客部署到星际文件系统(IPFS)

2023-02-21 · 3 min · 1326 words · Liudon

新冠疫情后的第一个春节

2023-02-16 · 1 min · 242 words · Liudon

January + 2

第一次清理键盘

2023-01-16 · 1 min · 141 words · Liudon

2022年终总结

2023-01-12 · 2 min · 646 words · Liudon

2022 + 18

August + 3

去掉Cloudflare烦人的email-decode.min.js请求

2022-08-26 · 1 min · 180 words · Liudon

累计布局偏移修复方案改进 —— 自动生成图片宽高

2022-08-24 · 3 min · 1157 words · Liudon

优化博客的累计布局偏移(CLS)问题

2022-08-20 · 2 min · 886 words · Liudon

July + 2

将博客部署到Cloudflare Pages

2022-07-29 · 2 min · 644 words · Liudon

奥林匹克公园向日葵之旅

2022-07-21 · 1 min · 179 words · Liudon

June + 1

记第二次洗牙

2022-06-21 · 1 min · 283 words · Liudon

May + 5

记录2022年海淀幼升小

2022-05-25 · 2 min · 715 words · Liudon

Golang解析json的一个问题

2022-05-20 · 1 min · 424 words · Liudon

疫情下的生活

2022-05-20 · 1 min · 189 words · Liudon

整理下博客的一些调整

2022-05-13 · 1 min · 331 words · Liudon

疫情下的五一假期

2022-05-05 · 1 min · 431 words · Liudon

April + 7

自己动手,更换thinkpad x1硬盘

2022-04-22 · 1 min · 209 words · Liudon

二刷百望山

2022-04-17 · 1 min · 209 words · Liudon

带娃游颐和园

2022-04-11 · 1 min · 258 words · Liudon

博客架构说明

2022-04-10 · 1 min · 433 words · Liudon

难得的清明假期

2022-04-06 · 1 min · 343 words · Liudon

十一年的等待,终于拿到了liudon.com域名

2022-04-01 · 2 min · 588 words · Liudon

被隔离的一周

2022-04-01 · 1 min · 328 words · Liudon

2020 + 3

December + 1

mysql中字符串和整型自动转换的问题

2020-12-14 · 1 min · 303 words · Liudon

May + 1

一次惊心动魄的Mysql更新操作

2020-05-19 · 2 min · 776 words · Liudon

January + 1

如何在北京公积金网站上修改婚姻状况

2020-01-17 · 1 min · 438 words · Liudon

2019 + 15

November + 2

PHP7.2编译安装后没有php.ini文件的问题

2019-11-26 · 1 min · 241 words · Liudon

检测网站支持的SSL/TLS协议版本

2019-11-14 · 1 min · 205 words · Liudon

October + 2

记一次难忘的手术经历

2019-10-28 · 2 min · 646 words · Liudon

十一假期经历

2019-10-08 · 1 min · 306 words · Liudon

September + 5

Swoft 框架运行分析(五) —— ConsoleProcessor模块分析

2019-09-26 · 10 min · 4524 words · Liudon

Swoft 框架运行分析(四) —— EventProcessor模块分析

2019-09-26 · 4 min · 1896 words · Liudon

一个git submodule update引发的问题

2019-09-06 · 4 min · 1748 words · Liudon

一个Curl的耗时长的问题

2019-09-04 · 2 min · 925 words · Liudon

Swoft 框架运行分析(三) —— BeanProcessor模块分析

2019-09-02 · 9 min · 4044 words · Liudon

August + 3

Swoft 框架运行分析(二) —— AnnotationProcessor模块分析

2019-08-29 · 4 min · 1656 words · Liudon

Swoft 框架运行分析(一)

2019-08-29 · 2 min · 966 words · Liudon

BCMath 与 科学计数

2019-08-16 · 1 min · 168 words · Liudon

March + 1

Flink Could Not Resolve Resourcemanager Address

2019-03-28 · 2 min · 517 words · Liudon

January + 2

解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错

2019-01-11 · 1 min · 336 words · Liudon

2019,新开始

2019-01-09 · 1 min · 250 words · Liudon
\ No newline at end of file diff --git a/archives/index.xml b/archives/index.xml new file mode 100644 index 000000000..52c96271a --- /dev/null +++ b/archives/index.xml @@ -0,0 +1,11 @@ + + + + 归档 on 流动 + https://liudon.com/archives/ + Recent content in 归档 on 流动 + Hugo -- 0.134.3 + zh-cn + + + diff --git a/assets/css/stylesheet.e58f8d6490878616473ed87168c3eb74d2244f30c48b524628d7c7a321c62e4e.css b/assets/css/stylesheet.e58f8d6490878616473ed87168c3eb74d2244f30c48b524628d7c7a321c62e4e.css new file mode 100644 index 000000000..7b0d1fe2b --- /dev/null +++ b/assets/css/stylesheet.e58f8d6490878616473ed87168c3eb74d2244f30c48b524628d7c7a321c62e4e.css @@ -0,0 +1,7 @@ +/* + PaperMod v7 + License: MIT https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE + Copyright (c) 2020 nanxiaobei and adityatelange + Copyright (c) 2021-2024 adityatelange +*/ +:root{--gap:24px;--content-gap:20px;--nav-width:1024px;--main-width:720px;--header-height:60px;--footer-height:60px;--radius:8px;--theme:rgb(255, 255, 255);--entry:rgb(255, 255, 255);--primary:rgb(30, 30, 30);--secondary:rgb(108, 108, 108);--tertiary:rgb(214, 214, 214);--content:rgb(31, 31, 31);--code-block-bg:rgb(28, 29, 33);--code-bg:rgb(245, 245, 245);--border:rgb(238, 238, 238)}.dark{--theme:rgb(29, 30, 32);--entry:rgb(46, 46, 51);--primary:rgb(218, 218, 219);--secondary:rgb(155, 156, 157);--tertiary:rgb(65, 66, 68);--content:rgb(196, 196, 197);--code-block-bg:rgb(46, 46, 51);--code-bg:rgb(55, 56, 62);--border:rgb(51, 51, 51)}.list{background:var(--code-bg)}.dark.list{background:var(--theme)}*,::after,::before{box-sizing:border-box}html{-webkit-tap-highlight-color:transparent;overflow-y:scroll;-webkit-text-size-adjust:100%;text-size-adjust:100%}a,button,body,h1,h2,h3,h4,h5,h6{color:var(--primary)}body{font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;font-size:18px;line-height:1.6;word-break:break-word;background:var(--theme)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section,table{display:block}h1,h2,h3,h4,h5,h6{line-height:1.2}h1,h2,h3,h4,h5,h6,p{margin-top:0;margin-bottom:0}ul{padding:0}a{text-decoration:none}body,figure,ul{margin:0}table{width:100%;border-collapse:collapse;border-spacing:0;overflow-x:auto;word-break:keep-all}button,input,textarea{padding:0;font:inherit;background:0 0;border:0}input,textarea{outline:0}button,input[type=button],input[type=submit]{cursor:pointer}input:-webkit-autofill,textarea:-webkit-autofill{box-shadow:0 0 0 50px var(--theme)inset}img{display:block;max-width:100%}.not-found{position:absolute;left:0;right:0;display:flex;align-items:center;justify-content:center;height:80%;font-size:160px;font-weight:700}.archive-posts{width:100%;font-size:16px}.archive-year{margin-top:40px}.archive-year:not(:last-of-type){border-bottom:2px solid var(--border)}.archive-month{display:flex;align-items:flex-start;padding:10px 0}.archive-month-header{margin:25px 0;width:200px}.archive-month:not(:last-of-type){border-bottom:1px solid var(--border)}.archive-entry{position:relative;padding:5px;margin:10px 0}.archive-entry-title{margin:5px 0;font-weight:400}.archive-count,.archive-meta{color:var(--secondary);font-size:14px}.footer,.top-link{font-size:12px;color:var(--secondary)}.footer{max-width:calc(var(--main-width) + var(--gap) * 2);margin:auto;padding:calc((var(--footer-height) - var(--gap))/2)var(--gap);text-align:center;line-height:24px}.footer span{margin-inline-start:1px;margin-inline-end:1px}.footer span:last-child{white-space:nowrap}.footer a{color:inherit;border-bottom:1px solid var(--secondary)}.footer a:hover{border-bottom:1px solid var(--primary)}.top-link{visibility:hidden;position:fixed;bottom:60px;right:30px;z-index:99;background:var(--tertiary);width:42px;height:42px;padding:12px;border-radius:64px;transition:visibility .5s,opacity .8s linear}.top-link,.top-link svg{filter:drop-shadow(0 0 0 var(--theme))}.footer a:hover,.top-link:hover{color:var(--primary)}.top-link:focus,#theme-toggle:focus{outline:0}.nav{display:flex;flex-wrap:wrap;justify-content:space-between;max-width:calc(var(--nav-width) + var(--gap) * 2);margin-inline-start:auto;margin-inline-end:auto;line-height:var(--header-height)}.nav a{display:block}.logo,#menu{display:flex;margin:auto var(--gap)}.logo{flex-wrap:inherit}.logo a{font-size:24px;font-weight:700}.logo a img,.logo a svg{display:inline;vertical-align:middle;pointer-events:none;transform:translate(0,-10%);border-radius:6px;margin-inline-end:8px}button#theme-toggle{font-size:26px;margin:auto 4px}body.dark #moon{vertical-align:middle;display:none}body:not(.dark) #sun{display:none}#menu{list-style:none;word-break:keep-all;overflow-x:auto;white-space:nowrap}#menu li+li{margin-inline-start:var(--gap)}#menu a{font-size:16px}#menu .active{font-weight:500;border-bottom:2px solid}.lang-switch li,.lang-switch ul,.logo-switches{display:inline-flex;margin:auto 4px}.lang-switch{display:flex;flex-wrap:inherit}.lang-switch a{margin:auto 3px;font-size:16px;font-weight:500}.logo-switches{flex-wrap:inherit}.main{position:relative;min-height:calc(100vh - var(--header-height) - var(--footer-height));max-width:calc(var(--main-width) + var(--gap) * 2);margin:auto;padding:var(--gap)}.page-header h1{font-size:40px}.pagination{display:flex}.pagination a{color:var(--theme);font-size:13px;line-height:36px;background:var(--primary);border-radius:calc(36px/2);padding:0 16px}.pagination .next{margin-inline-start:auto}.social-icons a{display:inline-flex;padding:10px}.social-icons a svg{height:26px;width:26px}code{direction:ltr}div.highlight,pre{position:relative}.copy-code{display:none;position:absolute;top:4px;right:4px;color:rgba(255,255,255,.8);background:rgba(78,78,78,.8);border-radius:var(--radius);padding:0 5px;font-size:14px;user-select:none}div.highlight:hover .copy-code,pre:hover .copy-code{display:block}.first-entry{position:relative;display:flex;flex-direction:column;justify-content:center;min-height:320px;margin:var(--gap)0 calc(var(--gap) * 2)}.first-entry .entry-header{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.first-entry .entry-header h1{font-size:34px;line-height:1.3}.first-entry .entry-content{margin:14px 0;font-size:16px;-webkit-line-clamp:3}.first-entry .entry-footer{font-size:14px}.home-info .entry-content{-webkit-line-clamp:unset}.post-entry{position:relative;margin-bottom:var(--gap);padding:var(--gap);background:var(--entry);border-radius:var(--radius);transition:transform .1s;border:1px solid var(--border)}.post-entry:active{transform:scale(.96)}.tag-entry .entry-cover{display:none}.entry-header h2{font-size:24px;line-height:1.3}.entry-content{margin:8px 0;color:var(--secondary);font-size:14px;line-height:1.6;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.entry-footer{color:var(--secondary);font-size:13px}.entry-link{position:absolute;left:0;right:0;top:0;bottom:0}.entry-hint{color:var(--secondary)}.entry-hint-parent{display:flex;justify-content:space-between}.entry-cover{font-size:14px;margin-bottom:var(--gap);text-align:center}.entry-cover img{border-radius:var(--radius);pointer-events:none;width:100%;height:auto}.entry-cover a{color:var(--secondary);box-shadow:0 1px 0 var(--primary)}.page-header,.post-header{margin:24px auto var(--content-gap)}.post-title{margin-bottom:2px;font-size:40px}.post-description{margin-top:10px;margin-bottom:5px}.post-meta,.breadcrumbs{color:var(--secondary);font-size:14px;display:flex;flex-wrap:wrap}.post-meta .i18n_list li{display:inline-flex;list-style:none;margin:auto 3px;box-shadow:0 1px 0 var(--secondary)}.breadcrumbs a{font-size:16px}.post-content{color:var(--content)}.post-content h3,.post-content h4,.post-content h5,.post-content h6{margin:24px 0 16px}.post-content h1{margin:40px auto 32px;font-size:40px}.post-content h2{margin:32px auto 24px;font-size:32px}.post-content h3{font-size:24px}.post-content h4{font-size:16px}.post-content h5{font-size:14px}.post-content h6{font-size:12px}.post-content a,.toc a:hover{box-shadow:0 1px;box-decoration-break:clone;-webkit-box-decoration-break:clone}.post-content a code{margin:auto 0;border-radius:0;box-shadow:0 -1px 0 var(--primary)inset}.post-content del{text-decoration:line-through}.post-content dl,.post-content ol,.post-content p,.post-content figure,.post-content ul{margin-bottom:var(--content-gap)}.post-content ol,.post-content ul{padding-inline-start:20px}.post-content li{margin-top:5px}.post-content li p{margin-bottom:0}.post-content dl{display:flex;flex-wrap:wrap;margin:0}.post-content dt{width:25%;font-weight:700}.post-content dd{width:75%;margin-inline-start:0;padding-inline-start:10px}.post-content dd~dd,.post-content dt~dt{margin-top:10px}.post-content table{margin-bottom:var(--content-gap)}.post-content table th,.post-content table:not(.highlighttable,.highlight table,.gist .highlight) td{min-width:80px;padding:8px 5px;line-height:1.5;border-bottom:1px solid var(--border)}.post-content table th{text-align:start}.post-content table:not(.highlighttable) td code:only-child{margin:auto 0}.post-content .highlight table{border-radius:var(--radius)}.post-content .highlight:not(table){margin:10px auto;background:var(--code-block-bg)!important;border-radius:var(--radius);direction:ltr}.post-content li>.highlight{margin-inline-end:0}.post-content ul pre{margin-inline-start:calc(var(--gap) * -2)}.post-content .highlight pre{margin:0}.post-content .highlighttable{table-layout:fixed}.post-content .highlighttable td:first-child{width:40px}.post-content .highlighttable td .linenodiv{padding-inline-end:0!important}.post-content .highlighttable td .highlight,.post-content .highlighttable td .linenodiv pre{margin-bottom:0}.post-content code{margin:auto 4px;padding:4px 6px;font-size:.78em;line-height:1.5;background:var(--code-bg);border-radius:2px}.post-content pre code{display:grid;margin:auto 0;padding:10px;color:#d5d5d6;background:var(--code-block-bg)!important;border-radius:var(--radius);overflow-x:auto;word-break:break-all}.post-content blockquote{margin:20px 0;padding:0 14px;border-inline-start:3px solid var(--primary)}.post-content hr{margin:30px 0;height:2px;background:var(--tertiary);border:0}.post-content iframe{max-width:100%}.post-content img{border-radius:4px;margin:1rem 0}.post-content img[src*="#center"]{margin:1rem auto}.post-content figure.align-center{text-align:center}.post-content figure>figcaption{color:var(--primary);font-size:16px;font-weight:700;margin:8px 0 16px}.post-content figure>figcaption>p{color:var(--secondary);font-size:14px;font-weight:400}.toc{margin:0 2px 40px;border:1px solid var(--border);background:var(--code-bg);border-radius:var(--radius);padding:.4em}.dark .toc{background:var(--entry)}.toc details summary{cursor:zoom-in;margin-inline-start:10px;user-select:none}.toc details[open] summary{cursor:zoom-out}.toc .details{display:inline;font-weight:500}.toc .inner{margin:5px 20px 0;padding:0 10px;opacity:.9}.toc li ul{margin-inline-start:var(--gap)}.toc summary:focus{outline:0}.post-footer{margin-top:56px}.post-footer>*{margin-bottom:10px}.post-tags{display:flex;flex-wrap:wrap;gap:10px}.post-tags li{display:inline-block}.post-tags a,.share-buttons,.paginav{border-radius:var(--radius);background:var(--code-bg);border:1px solid var(--border)}.post-tags a{display:block;padding:0 14px;color:var(--secondary);font-size:14px;line-height:34px;background:var(--code-bg)}.post-tags a:hover,.paginav a:hover{background:var(--border)}.share-buttons{padding:10px;display:flex;justify-content:center;overflow-x:auto;gap:10px}.share-buttons li,.share-buttons a{display:inline-flex}.share-buttons a:not(:last-of-type){margin-inline-end:12px}h1:hover .anchor,h2:hover .anchor,h3:hover .anchor,h4:hover .anchor,h5:hover .anchor,h6:hover .anchor{display:inline-flex;color:var(--secondary);margin-inline-start:8px;font-weight:500;user-select:none}.paginav{display:flex;line-height:30px}.paginav a{padding-inline-start:14px;padding-inline-end:14px;border-radius:var(--radius)}.paginav .title{letter-spacing:1px;text-transform:uppercase;font-size:small;color:var(--secondary)}.paginav .prev,.paginav .next{width:50%}.paginav span:hover:not(.title){box-shadow:0 1px}.paginav .next{margin-inline-start:auto;text-align:right}[dir=rtl] .paginav .next{text-align:left}h1>a>svg{display:inline}img.in-text{display:inline;margin:auto}.buttons,.main .profile{display:flex;justify-content:center}.main .profile{align-items:center;min-height:calc(100vh - var(--header-height) - var(--footer-height) - (var(--gap) * 2));text-align:center}.profile .profile_inner{display:flex;flex-direction:column;align-items:center;gap:10px}.profile img{border-radius:50%}.buttons{flex-wrap:wrap;max-width:400px}.button{background:var(--tertiary);border-radius:var(--radius);margin:8px;padding:6px;transition:transform .1s}.button-inner{padding:0 8px}.button:active{transform:scale(.96)}#searchbox input{padding:4px 10px;width:100%;color:var(--primary);font-weight:700;border:2px solid var(--tertiary);border-radius:var(--radius)}#searchbox input:focus{border-color:var(--secondary)}#searchResults li{list-style:none;border-radius:var(--radius);padding:10px;margin:10px 0;position:relative;font-weight:500}#searchResults{margin:10px 0;width:100%}#searchResults li:active{transition:transform .1s;transform:scale(.98)}#searchResults a{position:absolute;width:100%;height:100%;top:0;left:0;outline:none}#searchResults .focus{transform:scale(.98);border:2px solid var(--tertiary)}.terms-tags li{display:inline-block;margin:10px;font-weight:500}.terms-tags a{display:block;padding:3px 10px;background:var(--tertiary);border-radius:6px;transition:transform .1s}.terms-tags a:active{background:var(--tertiary);transform:scale(.96)}.bg{color:#cad3f5;background-color:#24273a}.chroma{color:#cad3f5;background-color:#24273a}.chroma .x{}.chroma .err{color:#ed8796}.chroma .cl{}.chroma .lnlinks{outline:none;text-decoration:none;color:inherit}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#474733}.chroma .lnt{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .ln{white-space:pre;-webkit-user-select:none;user-select:none;margin-right:.4em;padding:0 .4em;color:#8087a2}.chroma .line{display:flex}.chroma .k{color:#c6a0f6}.chroma .kc{color:#f5a97f}.chroma .kd{color:#ed8796}.chroma .kn{color:#8bd5ca}.chroma .kp{color:#c6a0f6}.chroma .kr{color:#c6a0f6}.chroma .kt{color:#ed8796}.chroma .n{}.chroma .na{color:#8aadf4}.chroma .nb{color:#91d7e3}.chroma .bp{color:#91d7e3}.chroma .nc{color:#eed49f}.chroma .no{color:#eed49f}.chroma .nd{color:#8aadf4;font-weight:700}.chroma .ni{color:#8bd5ca}.chroma .ne{color:#f5a97f}.chroma .nf{color:#8aadf4}.chroma .fm{color:#8aadf4}.chroma .nl{color:#91d7e3}.chroma .nn{color:#f5a97f}.chroma .nx{}.chroma .py{color:#f5a97f}.chroma .nt{color:#c6a0f6}.chroma .nv{color:#f4dbd6}.chroma .vc{color:#f4dbd6}.chroma .vg{color:#f4dbd6}.chroma .vi{color:#f4dbd6}.chroma .vm{color:#f4dbd6}.chroma .l{}.chroma .ld{}.chroma .s{color:#a6da95}.chroma .sa{color:#ed8796}.chroma .sb{color:#a6da95}.chroma .sc{color:#a6da95}.chroma .dl{color:#8aadf4}.chroma .sd{color:#6e738d}.chroma .s2{color:#a6da95}.chroma .se{color:#8aadf4}.chroma .sh{color:#6e738d}.chroma .si{color:#a6da95}.chroma .sx{color:#a6da95}.chroma .sr{color:#8bd5ca}.chroma .s1{color:#a6da95}.chroma .ss{color:#a6da95}.chroma .m{color:#f5a97f}.chroma .mb{color:#f5a97f}.chroma .mf{color:#f5a97f}.chroma .mh{color:#f5a97f}.chroma .mi{color:#f5a97f}.chroma .il{color:#f5a97f}.chroma .mo{color:#f5a97f}.chroma .o{color:#91d7e3;font-weight:700}.chroma .ow{color:#91d7e3;font-weight:700}.chroma .p{}.chroma .c{color:#6e738d;font-style:italic}.chroma .ch{color:#6e738d;font-style:italic}.chroma .cm{color:#6e738d;font-style:italic}.chroma .c1{color:#6e738d;font-style:italic}.chroma .cs{color:#6e738d;font-style:italic}.chroma .cp{color:#6e738d;font-style:italic}.chroma .cpf{color:#6e738d;font-weight:700;font-style:italic}.chroma .g{}.chroma .gd{color:#ed8796;background-color:#363a4f}.chroma .ge{font-style:italic}.chroma .gr{color:#ed8796}.chroma .gh{color:#f5a97f;font-weight:700}.chroma .gi{color:#a6da95;background-color:#363a4f}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#f5a97f;font-weight:700}.chroma .gt{color:#ed8796}.chroma .gl{text-decoration:underline}.chroma .w{}.chroma{background-color:unset!important}.chroma .hl{display:flex}.chroma .lnt{padding:0 0 0 12px}.highlight pre.chroma code{padding:8px 0}.highlight pre.chroma .line .cl,.chroma .ln{padding:0 10px}.chroma .lntd:last-of-type{width:100%}::-webkit-scrollbar-track{background:0 0}.list:not(.dark)::-webkit-scrollbar-track{background:var(--code-bg)}::-webkit-scrollbar-thumb{background:var(--tertiary);border:5px solid var(--theme);border-radius:var(--radius)}.list:not(.dark)::-webkit-scrollbar-thumb{border:5px solid var(--code-bg)}::-webkit-scrollbar-thumb:hover{background:var(--secondary)}::-webkit-scrollbar:not(.highlighttable,.highlight table,.gist .highlight){background:var(--theme)}.post-content .highlighttable td .highlight pre code::-webkit-scrollbar{display:none}.post-content :not(table) ::-webkit-scrollbar-thumb{border:2px solid var(--code-block-bg);background:#717175}.post-content :not(table) ::-webkit-scrollbar-thumb:hover{background:#a3a3a5}.gist table::-webkit-scrollbar-thumb{border:2px solid #fff;background:#adadad}.gist table::-webkit-scrollbar-thumb:hover{background:#707070}.post-content table::-webkit-scrollbar-thumb{border-width:2px}@media screen and (min-width:768px){::-webkit-scrollbar{width:19px;height:11px}}@media screen and (max-width:768px){:root{--gap:14px}.profile img{transform:scale(.85)}.first-entry{min-height:260px}.archive-month{flex-direction:column}.archive-year{margin-top:20px}.footer{padding:calc((var(--footer-height) - var(--gap) - 10px)/2)var(--gap)}}@media screen and (max-width:900px){.list .top-link{transform:translateY(-5rem)}}@media screen and (max-width:340px){.share-buttons{justify-content:unset}}@media(prefers-reduced-motion){.terms-tags a:active,.button:active,.post-entry:active,.top-link,#searchResults .focus,#searchResults li:active{transform:none}}:root{--primary:#212121;--content:#333333;--code-bg:rgba(175, 184, 193, 0.2);--hljs-bg:#282C34}.dark{--code-bg:rgba(175, 184, 193, 0.2);--hljs-bg:#1c1c1c}.post-content a{color:#0969da;box-shadow:none;text-decoration:none}.post-content a:hover{text-decoration:underline}.post-content code{margin:unset}.post-content pre code{max-height:40rem}pre,code{font-family:Consolas,Monaco,SFMono-Regular,Andale Mono,Liberation Mono,Ubuntu Mono,Menlo,monospace}.post-content .highlight pre{tab-size:4!important}.post-content .highlight:not(table),.post-content pre{box-shadow:1px 5px 5px 1px rgb(144 164 174/.6);border-radius:var(--radius)}.dark .post-content .highlight:not(table),.post-content pre{box-shadow:1px 5px 5px 1px rgb(5 10 15/.6);border-radius:var(--radius)}.post-content img{margin:auto}body{font-family:-apple-system,BlinkMacSystemFont,avenir next,Avenir,nimbus sans l,Roboto,Noto,segoe ui,Arial,Helvetica,helvetica neue,sans-serif;font-size:1rem;line-height:1.5;margin:0}.post-content{padding-top:1rem}.post-content blockquote{color:gray}.post-content p,.post-content blockquote,.post-content figure,.post-content table,.post-content hr{margin:1.15rem 0}.post-content ul,.post-content ol,.post-content dl,.post-content li{margin:.5rem 0}.post-content h1,.post-content h2,.post-content h3,.post-content h4,.post-content h5,.post-content h6{margin-bottom:1.15rem;font-weight:600}.post-content h1{font-size:2.6rem;margin-top:4rem;border-bottom:1px solid #ccc}.post-content h2{font-size:1.8rem;margin-top:4rem;border-bottom:1px solid #ccc}.post-content h3{font-size:1.6rem;margin-top:2rem}.post-content h4{font-size:1.4rem;margin-top:1.44rem}.post-content h5{font-size:1.2rem;margin-top:1.15rem}.post-content h6{font-size:1rem;margin-top:1rem}.post-content table tr{border:1px solid #979da3!important}.post-content table tr:nth-child(2n),.post-content thead{background-color:var(--code-bg)}.post-content table th{border:1px solid #979da3!important}.post-content table td{border:1px solid #979da3!important}:root{--nav-width:1380px;--article-width:650px;--toc-width:300px}.toc{margin:0 2px 40px;border:1px solid var(--border);background:var(--entry);border-radius:var(--radius);padding:.4em}.toc-container.wide{position:absolute;height:100%;border-right:1px solid var(--border);left:calc((var(--toc-width) + var(--gap)) * -1);top:calc(var(--gap) * 2);width:var(--toc-width)}.wide .toc{position:sticky;top:var(--gap);border:unset;background:unset;border-radius:unset;width:100%;margin:0 2px 40px}.toc details summary{cursor:zoom-in;margin-inline-start:20px;padding:12px 0}.toc details[open] summary{font-weight:500}.toc-container.wide .toc .inner{margin:0}.active{font-size:110%;font-weight:600}.toc ul{list-style-type:circle}.toc .inner{margin:0 0 0 20px;padding:0 15px 15px 20px;font-size:16px;max-height:83vh;overflow-y:auto}.toc .inner::-webkit-scrollbar-thumb{background:var(--border);border:7px solid var(--theme);border-radius:var(--radius)}.toc li ul{margin-inline-start:calc(var(--gap) * .5);list-style-type:none}.toc li{list-style:none;font-size:.95rem;padding-bottom:5px}.toc li a:hover{color:var(--secondary)}img{width:100%;height:auto}video{width:100%;height:auto}.post-content figure>figcaption{color:var(--primary);font-size:12px;margin:8px 0 16px;font-weight:100}figure figcaption:before{content:"◎ "}.friendurl{text-decoration:none!important;color:#000;box-shadow:none!important}.myfriend{width:56px!important;height:56px!important;border-radius:50%!important;padding:2px;margin-top:20px!important;margin-left:14px!important;background-color:#fff}.frienddiv{overflow:auto;height:100px;width:49%;display:inline-block!important;border-radius:5px;background:0 0;-webkit-transition:all ease-out .3s;-moz-transition:all ease-out .3s;-o-transition:all ease-out .3s;transition:all ease-out .3s}.dark .frienddiv:hover{background:var(--code-bg)}.frienddiv:hover{background:var(--theme);transition:transform 1s;webkit-transform:scale(1.1);-moz-transform:scale(1.2);-ms-transform:scale(1.2);-o-transform:scale(1.2);transform:scale(1.1)}.frienddiv:hover .frienddivleft img{transition:.9s!important;-webkit-transition:.9s!important;-moz-transition:.9s!important;-o-transition:.9s!important;-ms-transition:.9s!important;transform:rotate(360deg)!important;-webkit-transform:rotate(360deg)!important;-moz-transform:rotate(360deg)!important;-o-transform:rotate(360deg)!important;-ms-transform:rotate(360deg)!important}.frienddivleft{width:92px;float:left;margin-right:-5px}.frienddivright{margin-top:18px;margin-right:18px}.friendname{text-overflow:ellipsis;font-size:100%;margin-bottom:5px;color:var(--primary)}.friendinfo{text-overflow:ellipsis;font-size:70%;color:var(--primary)}@media screen and (max-width:600px){.friendinfo{display:none}.frienddivleft{width:84px;margin:auto}.frienddivright{height:100%;margin:auto;display:flex;align-items:center;justify-content:center}.friendname{font-size:18px}}.post-copyright{margin:2em 0;padding:.5em 1em;border-left:3px solid #ff1700;list-style:none;background:var(--code-bg)}.post-content p:has(>picture:nth-child(2)){column-count:2;column-gap:8px;margin:6px 0}.post-content p:has(>picture:nth-child(3)){column-count:3}.post-content p:has(>picture:nth-child(4)){column-count:4}.post-content p:has(>picture:nth-child(5)){column-count:5}.post-content p:has(>picture:nth-child(6)){column-count:4}.post-content p:has(>picture:nth-child(2)) picture{display:inherit}.post-content p:has(>picture:nth-child(6)) picture{margin-bottom:8px}.view-image-lead img{width:unset!important;height:unset!important} \ No newline at end of file diff --git a/assets/js/search.c0b8610e611c28b414460f80b219a3abd0955217a8759ed059118a6326ff095f.js b/assets/js/search.c0b8610e611c28b414460f80b219a3abd0955217a8759ed059118a6326ff095f.js new file mode 100644 index 000000000..862f4bf2b --- /dev/null +++ b/assets/js/search.c0b8610e611c28b414460f80b219a3abd0955217a8759ed059118a6326ff095f.js @@ -0,0 +1,19 @@ +/** + * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2023 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?O.getFn:n,o=t.fieldNormWeight,a=void 0===o?O.fieldNormWeight:o;r(this,e),this.norm=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(j).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),a=parseFloat(Math.round(o*r)/r);return n.set(i,a),a},clear:function(){n.clear()}}}(a,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,f(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();f(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?O.getFn:r,o=n.fieldNormWeight,a=void 0===o?O.fieldNormWeight:o,c=new A({getFn:i,fieldNormWeight:a});return c.setKeys(e.map(x)),c.setSources(t),c.create(),c}function I(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,a=t.expectedLocation,c=void 0===a?0:a,s=t.distance,h=void 0===s?O.distance:s,u=t.ignoreLocation,l=void 0===u?O.ignoreLocation:u,d=r/e.length;if(l)return d;var f=Math.abs(c-o);return h?d+f/h:f?1:d}var F=32;function C(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?O.location:i,a=r.distance,c=void 0===a?O.distance:a,s=r.threshold,h=void 0===s?O.threshold:s,u=r.findAllMatches,l=void 0===u?O.findAllMatches:u,d=r.minMatchCharLength,f=void 0===d?O.minMatchCharLength:d,v=r.includeMatches,g=void 0===v?O.includeMatches:v,y=r.ignoreLocation,p=void 0===y?O.ignoreLocation:y;if(t.length>F)throw new Error("Pattern length exceeds max of ".concat(F,"."));for(var m,b=t.length,k=e.length,M=Math.max(0,Math.min(o,k)),w=h,x=M,L=f>1||g,S=L?Array(k):[];(m=e.indexOf(t,x))>-1;){var _=I(t,{currentLocation:m,expectedLocation:M,distance:c,ignoreLocation:p});if(w=Math.min(_,w),x=m+b,L)for(var j=0;j=$;z-=1){var J=z-1,R=n[e.charAt(J)];if(L&&(S[J]=+!!R),K[z]=(K[z+1]<<1|1)&R,P&&(K[z]|=(A[z+1]|A[z])<<1|1|A[z+1]),K[z]&N&&(E=I(t,{errors:P,currentLocation:J,expectedLocation:M,distance:c,ignoreLocation:p}))<=w){if(w=E,(x=J)<=M)break;$=Math.max(1,2*M-x)}}if(I(t,{errors:P+1,currentLocation:M,expectedLocation:M,distance:c,ignoreLocation:p})>w)break;A=K}var U={isMatch:x>=0,score:Math.max(.001,E)};if(L){var B=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:O.minMatchCharLength,n=[],r=-1,i=-1,o=0,a=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}(S,f);B.length?g&&(U.indices=B):U.isMatch=!1}return U}function N(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,a=void 0===o?O.location:o,c=i.threshold,s=void 0===c?O.threshold:c,h=i.distance,u=void 0===h?O.distance:h,l=i.includeMatches,d=void 0===l?O.includeMatches:l,f=i.findAllMatches,v=void 0===f?O.findAllMatches:f,g=i.minMatchCharLength,y=void 0===g?O.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?O.isCaseSensitive:p,b=i.ignoreLocation,k=void 0===b?O.ignoreLocation:b;if(r(this,e),this.options={location:a,threshold:s,distance:u,includeMatches:d,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:k},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var M=function(e,t){n.chunks.push({pattern:e,alphabet:N(e),startIndex:t})},w=this.pattern.length;if(w>F){for(var x=0,L=w%F,S=w-L;x-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function D(e,t){t.score=e.score}var K=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;if(r(this,e),this.options=t(t({},O),i),this.options.useExtendedSearch)throw new Error("Extended search is not available");this._keyStore=new w(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof A))throw new Error("Incorrect 'index' type");this._myIndex=t||E(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){y(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{}).limit,n=void 0===t?-1:t,r=this.options,i=r.includeMatches,o=r.includeScore,a=r.shouldSort,c=r.sortFn,s=r.ignoreFieldNorm,h=f(e)?f(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return function(e,t){var n=t.ignoreFieldNorm,r=void 0===n?O.ignoreFieldNorm:n;e.forEach((function(e){var t=1;e.matches.forEach((function(e){var n=e.key,i=e.norm,o=e.score,a=n?n.weight:null;t*=Math.pow(0===o&&a?Number.EPSILON:o,(a||1)*(r?1:i))})),e.score=t}))}(h,{ignoreFieldNorm:s}),a&&h.sort(c),v(n)&&n>-1&&(h=h.slice(0,n)),function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?O.includeMatches:r,o=n.includeScore,a=void 0===o?O.includeScore:o,c=[];return i&&c.push($),a&&c.push(D),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return c.length&&c.forEach((function(t){t(e,r)})),r}))}(h,this._docs,{includeMatches:i,includeScore:o})}},{key:"_searchStringList",value:function(e){var t=T(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(y(n)){var a=t.searchIn(n),c=a.isMatch,s=a.score,h=a.indices;c&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:h}]})}})),r}},{key:"_searchLogical",value:function(e){throw new Error("Logical search is not available")}},{key:"_searchObjectList",value:function(e){var t=this,n=T(e,this.options),r=this._myIndex,i=r.keys,o=r.records,a=[];return o.forEach((function(e){var r=e.$,o=e.i;if(y(r)){var s=[];i.forEach((function(e,i){s.push.apply(s,c(t._findMatches({key:e,value:r[i],searcher:n})))})),s.length&&a.push({idx:o,item:r,matches:s})}})),a}},{key:"_findMatches",value:function(e){var t=e.key,n=e.value,r=e.searcher;if(!y(n))return[];var i=[];if(u(n))n.forEach((function(e){var n=e.v,o=e.i,a=e.n;if(y(n)){var c=r.searchIn(n),s=c.isMatch,h=c.score,u=c.indices;s&&i.push({score:h,key:t,value:n,idx:o,norm:a,indices:u})}}));else{var o=n.v,a=n.n,c=r.searchIn(o),s=c.isMatch,h=c.score,l=c.indices;s&&i.push({score:h,key:t,value:o,norm:a,indices:l})}return i}}]),e}();return K.version="7.0.0",K.createIndex=E,K.parseIndex=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?O.getFn:n,i=t.fieldNormWeight,o=void 0===i?O.fieldNormWeight:i,a=e.keys,c=e.records,s=new A({getFn:r,fieldNormWeight:o});return s.setKeys(a),s.setIndexRecords(c),s},K.config=O,K},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); +; +/* + PaperMod v7 + License: MIT https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE + Copyright (c) 2020 nanxiaobei and adityatelange + Copyright (c) 2021-2024 adityatelange +*/ + +; +(()=>{var i,r,c,e={distance:1e3,iscasesensitive:!1,keys:["title","content"],location:0,minmatchcharlength:0,shouldsort:!0,threshold:.4},n=document.getElementById("searchResults"),t=document.getElementById("searchInput"),a=null,s=!1;window.onload=function(){var t=new XMLHttpRequest;t.onreadystatechange=function(){if(t.readyState===4)if(t.status===200){var n,s=JSON.parse(t.responseText);s&&(n={distance:100,threshold:.4,ignoreLocation:!0,keys:["title","permalink","summary","content"]},e&&(n={isCaseSensitive:!!e.iscasesensitive&&e.iscasesensitive,includeScore:!!e.includescore&&e.includescore,includeMatches:!!e.includematches&&e.includematches,minMatchCharLength:e.minmatchcharlength?e.minmatchcharlength:1,shouldSort:!e.shouldsort||e.shouldsort,findAllMatches:!!e.findallmatches&&e.findallmatches,keys:e.keys?e.keys:["title","permalink","summary","content"],location:e.location?e.location:0,threshold:e.threshold?e.threshold:.4,distance:e.distance?e.distance:100,ignoreLocation:!e.ignorelocation||e.ignorelocation}),i=new Fuse(s,n))}else console.log(t.responseText)},t.open("GET","../search.json"),t.send()};function o(e){document.querySelectorAll(".focus").forEach(function(e){e.classList.remove("focus")}),e?(e.focus(),document.activeElement=a=e,e.parentElement.classList.add("focus")):document.activeElement.parentElement.classList.add("focus")}function l(){s=!1,n.innerHTML=t.value="",t.focus()}t.onkeyup=function(){if(i){const e=i.search(this.value.trim());if(e.length!==0){let t="";for(let n in e)t+=`
  • ${e[n].item.title} \xBB
  • `;n.innerHTML=t,s=!0,r=n.firstChild,c=n.lastChild}else s=!1,n.innerHTML=""}},t.addEventListener("search",function(){this.value||l()}),document.onkeydown=function(e){let d=e.key;var u,i=document.activeElement;let h=document.getElementById("searchbox").contains(i);if(i===t)for(u=document.getElementsByClassName("focus");u.length>0;)u[0].classList.remove("focus");else a&&(i=a);if(d==="Escape")l();else if(!s||!h)return;else d==="ArrowDown"?(e.preventDefault(),i==t?o(n.firstChild.lastChild):i.parentElement!=c&&o(i.parentElement.nextSibling.lastChild)):d==="ArrowUp"?(e.preventDefault(),i.parentElement==r?o(t):i!=t&&o(i.parentElement.previousSibling.lastChild)):d==="ArrowRight"&&i.click()}})() \ No newline at end of file diff --git a/avatar.png b/avatar.png new file mode 100644 index 000000000..c32e70549 Binary files /dev/null and b/avatar.png differ diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..1b39f64d7 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,5 @@ +分类 | 流动 +
      \ No newline at end of file diff --git a/categories/index.xml b/categories/index.xml new file mode 100644 index 000000000..f7d682c14 --- /dev/null +++ b/categories/index.xml @@ -0,0 +1,11 @@ + + + + 分类 on 流动 + https://liudon.com/categories/ + Recent content in 分类 on 流动 + Hugo -- 0.134.3 + zh-cn + + + diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 000000000..2d6e1857c Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 000000000..2d6e1857c Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 000000000..62cf7485b Binary files /dev/null and b/favicon.ico differ diff --git a/functions/analytics/[index].js b/functions/analytics/[index].js new file mode 100644 index 000000000..8bc9ca1d4 --- /dev/null +++ b/functions/analytics/[index].js @@ -0,0 +1,44 @@ +export async function onRequest(context) { + try { + return await postHandler(context); + } catch(e) { + return new Response(`${e.message}\n${e.stack}`, { status: 500 }); + } +} + +async function postHandler(context) { + const GA_DOMAIN = 'google-analytics.com'; + const GA_COLLECT_PATH = 'g\/collect'; + const COLLECT_PATH = 'analytics/post'; + const DOMAIN = context.request.headers.get('host'); + + const url = context.request.url; + const cf_ip = context.request.headers.get('CF-Connecting-IP'); + const cf_country = context.request.cf.country; + const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`) + `&up.IP=${cf_ip}&up.IPCountry=${cf_country}` + const newReq = await readRequest(context.request, ga_url); + context.waitUntil(fetch(newReq)); + + return new Response(null, { + status: 204, + statusText: 'No Content', + }); +} + +async function readRequest(request, url) { + const { _, headers } = request; + const nq = { + method: request.method, + headers: { + Origin: headers.get('origin'), + 'Cache-Control': 'max-age=0', + 'User-Agent': headers.get('user-agent'), + Accept: headers.get('accept'), + 'Accept-Language': headers.get('accept-language'), + 'Content-Type': headers.get('content-type') || 'text/plain', + Referer: headers.get('referer'), + }, + body: request.body, + }; + return new Request(url, nq); + } \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..ce92c5885 --- /dev/null +++ b/index.html @@ -0,0 +1,64 @@ +流动 +

      Hi there 👋

      Welcome to my blog

      Github Pages 部署流程解析

      上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。 +看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。 +- name: Install dependencies run: pnpm install - name: Build run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload dist repository path: './dist' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 核心逻辑就是上面这段。 +...

      2024-09-24 · 2 min · 570 words · Liudon

      搭建个人锻炼页面

      工作的缘故,平时基本一坐一天,缺少运动。 +时间久了,各种毛病也就出来了。 +搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。 +坚持了一段时间,也不了了之了。 +...

      2024-09-22 · 1 min · 325 words · Liudon

      你好 Follow

      Follow: Next generation information browser. +最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。 +蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。 +...

      2024-09-17 · 1 min · 271 words · Liudon

      中秋爬山

      中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。 +晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。 +...

      2024-09-16 · 2 min · 704 words · Liudon

      Google Adsense的审核之旅

      中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。 +偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。 +...

      2024-09-16 · 1 min · 411 words · Liudon

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。 +背景 周末更新博客时,发现workflow的上传IPFS任务执行失败了。 +...

      2024-09-04 · 3 min · 1246 words · Liudon

      一次简短的青岛之行

      刚放暑假的时候,就答应了娃带她去一趟青岛。 +8月份要回老家,所以定在了7月中下旬出发。 +车票/酒店都订好了,结果来了个台风格美。 +出发前一周一直在查天气,就怕去了一直下雨。 +...

      2024-08-31 · 2 min · 998 words · Liudon

      解决 "undeclared name: any (requires version go1.18 or later)" 编译错误

      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ $ protoc-gen-go --version protoc-gen-go v1.34.2 $ $ sh make.sh user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) $ 流水线编译报错,其中make.sh文件代码: +... protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto ... go build 同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。 +...

      2024-06-14 · 1 min · 473 words · Liudon

      搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

      背景 4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。 +没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。 +...

      2024-05-22 · 3 min · 1262 words · Liudon

      302跳转的跨域问题(CORS)

      302跳转的跨域问题 +场景一:302不返回跨域头 请求 +GET /302 HTTP/1.1 Host: liudon.xyz Origin: https://www.baidu.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 返回 +HTTP/1.1 200 OK Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Cf-Ray: 88535773eaf5107e-HKG Content-Length: 143 Content-Type: text/html Date: Fri, 17 May 2024 11:42:00 GMT Expires: Thu, 01 Jan 1970 00:00:01 GMT Location: https://liudon.org Server: cloudflare Vary: Accept-Encoding 浏览器报错 +...

      2024-05-17 · 2 min · 721 words · Liudon

      GORM增加sqlcommenter特性

      什么是sqlcommenter? +sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side. +...

      2024-04-18 · 2 min · 742 words · Liudon

      源码分析:GORM是如何生成sql的

      在gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。 +gorm使用示例 +package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) type Product struct { gorm.Model Code string Price uint } func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) var product Product db.First(&product, 1) // 根据整型主键查找 } 我们以First查询为例,看一下是怎么转成具体sql的。 +...

      2024-04-18 · 6 min · 2737 words · Liudon

      工银亚洲网银密码重置

      18年的时候办了张工银亚洲的银行卡,好几年没有用过了。 +今年想起来了,发现网银登不上了,密码忘了。 +最悲剧的是,试了超过10次,账户冻结了。 +打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。 +...

      2024-03-16 · 1 min · 481 words · Liudon

      加速Cloudflare访问

      背景 这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。 +众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。 +...

      2024-02-21 · 3 min · 1128 words · Liudon

      2023年终总结

      2023年过完了,是时候来个总结了。 +博客 2023年一共更新了15篇内容,共计12000字。 +访问Top3的文章: +...

      2024-01-04 · 2 min · 748 words · Liudon

      2023年12月北京暴雪记录

      记录暴雪下普通打工人的生活。 +12月14日 周四 北京的雪已经连着下了两天了。 +12月11日,也是因为下雪,晚上打车打到10点半才叫到车。 +所以这次下雪后,晚上就早走了。 +...

      2023-12-16 · 2 min · 599 words · Liudon

      使用Hugo实现响应式和优化的图片

      继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。 +问题 在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高。 +经过一段运行后,发现这里有一个弊端。 +Run hugo --gc --minify --cleanDestinationDir Start building sites … hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer 随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。 +...

      2023-12-10 · 5 min · 2021 words · Liudon

      加速Google Analytics

      起因 Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。 +最近在优化页面访问速度,发现Google Analytics是一个优化点。 +优化 1. 访问加速 国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。 +...

      2023-12-02 · 2 min · 870 words · Liudon

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。 +今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。 +...

      2023-10-27 · 2 min · 635 words · Liudon

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon
      \ No newline at end of file diff --git a/index.json b/index.json new file mode 100644 index 000000000..9327fc511 --- /dev/null +++ b/index.json @@ -0,0 +1 @@ +{"version":"https://jsonfeed.org/version/1.1","title":"流动","home_page_url":"https://liudon.com/","feed_url":"https://liudon.com/index.json","authors":[{"name":"Liudon"}],"items":[{"title":"Github Pages 部署流程解析","id":"https://liudon.com/posts/github-pages-deployment-tutorial/","url":"https://liudon.com/posts/github-pages-deployment-tutorial/","summary":"\u003cp\u003e上周末在\u003ca href=\"https://liudon.com/posts/building-a-workout-page/\"\u003e搭建个人锻炼页面\u003c/a\u003e时,遇到个\u003ccode\u003eGithub Pages\u003c/code\u003e部署的困惑。\u003c/p\u003e\n\u003cp\u003e看了\u003ccode\u003erunning_page\u003c/code\u003e项目文档,是支持部署到\u003ccode\u003eGithub Pages\u003c/code\u003e页面的,对应的操作流程定义在\u003ca href=\"https://github.com/yihong0618/running_page/blob/master/.github/workflows/gh-pages.yml\"\u003egithub/workflows/gh-pages.yml\u003c/a\u003e文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Install dependencies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: pnpm install\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: PATH_PREFIX\u003cspan style=\"color:#f92672\"\u003e=/$\u003c/span\u003e{{ github\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erepository\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ename }} pnpm build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Upload artifact\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: actions\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eupload\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epages\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eartifact\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e with:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Upload dist repository\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e path: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./dist\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Deploy to GitHub Pages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e id: deployment\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: actions\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003edeploy\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epages\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev4\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e核心逻辑就是上面这段。\u003c/p\u003e","content_html":"\u003cp\u003e上周末在\u003ca href=\"https://liudon.com/posts/building-a-workout-page/\"\u003e搭建个人锻炼页面\u003c/a\u003e时,遇到个\u003ccode\u003eGithub Pages\u003c/code\u003e部署的困惑。\u003c/p\u003e\n\u003cp\u003e看了\u003ccode\u003erunning_page\u003c/code\u003e项目文档,是支持部署到\u003ccode\u003eGithub Pages\u003c/code\u003e页面的,对应的操作流程定义在\u003ca href=\"https://github.com/yihong0618/running_page/blob/master/.github/workflows/gh-pages.yml\"\u003egithub/workflows/gh-pages.yml\u003c/a\u003e文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Install dependencies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: pnpm install\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: PATH_PREFIX\u003cspan style=\"color:#f92672\"\u003e=/$\u003c/span\u003e{{ github\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erepository\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ename }} pnpm build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Upload artifact\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: actions\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eupload\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epages\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eartifact\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e with:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Upload dist repository\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e path: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./dist\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Deploy to GitHub Pages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e id: deployment\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: actions\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003edeploy\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epages\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev4\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e核心逻辑就是上面这段。\u003c/p\u003e\n\u003cp\u003e之前搞过部署\u003ccode\u003ehugo\u003c/code\u003e静态站点到\u003ccode\u003eGithub Pages\u003c/code\u003e,使用的分支方式部署,编译后的静态文件单独用一个分支存放。\u003c/p\u003e\n\u003cp\u003e这里以我自己的博客项目举例,大致流程如下图:\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-214938_hu482132360433279231.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-214938_hu7283675988670190119.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240924-214938.png\" width=\"1506\" height=\"398\" alt=\"github-pages-deploy-flow\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e按我的理解,这里最终访问的文件内容是存在\u003ccode\u003egh-page\u003c/code\u003e分支下的。\u003c/p\u003e\n\u003cp\u003e但是实际部署完\u003ccode\u003erunning_page\u003c/code\u003e项目后,我发现并没有出现\u003ccode\u003egh-page\u003c/code\u003e分支,但是\u003ccode\u003eGithub Pages\u003c/code\u003e却可以正常访问。\u003c/p\u003e\n\u003cp\u003e有点不可思议,这个访问的数据是在哪里的呢?\u003c/p\u003e\n\u003cp\u003e带着这个疑问,在v2ex上发了个\u003ca href=\"https://www.v2ex.com/t/1074875\"\u003e咨询贴\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e经过网友解惑,大致搞明白了这里的流程:\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-215958_hu7195499678805036272.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-215958_hu18258395951143914551.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240924-215958.png\" width=\"1958\" height=\"898\" alt=\"github-pages-deploy-flow\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eGithub Pages\u003c/code\u003e的发布源有两种方式,通过分支部署和通过\u003ccode\u003eGithub Actions\u003c/code\u003e部署,分别对应上图的两条分支。\u003c/p\u003e\n\u003cp\u003e这里最终都会将build后的静态文件部署到\u003ccode\u003eGithub Pages\u003c/code\u003e服务上,供用户访问。\u003c/p\u003e\n\u003cp\u003e分支部署的方式,其实是有一个\u003ca href=\"https://github.com/Liudon/liudon.github.io/actions/workflows/pages/pages-build-deployment\"\u003e内置工作流\u003c/a\u003e部署到\u003ccode\u003eGithub Pages\u003c/code\u003e服务上的。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-220607_hu5032447485174126128.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/github-pages-deployment-tutorial/20240924-220607_hu12933780071938403223.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240924-220607.png\" width=\"1760\" height=\"898\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e整个部署流程大致就是这样,最终的静态文件都是存在\u003ccode\u003eGithub Pages\u003c/code\u003e服务上的。\u003c/p\u003e\n","date_published":"2024-09-24T21:30:21+08:00","tags":["github pages"]},{"title":"搭建个人锻炼页面","id":"https://liudon.com/posts/building-a-workout-page/","url":"https://liudon.com/posts/building-a-workout-page/","summary":"\u003cp\u003e工作的缘故,平时基本一坐一天,缺少运动。\u003c/p\u003e\n\u003cp\u003e时间久了,各种毛病也就出来了。\u003c/p\u003e\n\u003cp\u003e搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。\u003c/p\u003e\n\u003cp\u003e坚持了一段时间,也不了了之了。\u003c/p\u003e","content_html":"\u003cp\u003e工作的缘故,平时基本一坐一天,缺少运动。\u003c/p\u003e\n\u003cp\u003e时间久了,各种毛病也就出来了。\u003c/p\u003e\n\u003cp\u003e搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。\u003c/p\u003e\n\u003cp\u003e坚持了一段时间,也不了了之了。\u003c/p\u003e\n\u003cp\u003e今年开始,决定骑车通勤,上下班路上运动一下。\u003c/p\u003e\n\u003cp\u003e最近在别人博客里发现了运动记录,发现是通过\u003ca href=\"https://github.com/yihong0618/running_page\"\u003erunning_page\u003c/a\u003e项目实现的。\u003c/p\u003e\n\u003cp\u003e顺藤摸瓜,又发现了\u003ca href=\"https://github.com/ben-29/workouts_page\"\u003eworkouts_page\u003c/a\u003e项目,支持多种运动。\u003c/p\u003e\n\u003cp\u003e于是看文档,部署起来,我的\u003ca href=\"https://workout.liudon.com/\"\u003e个人锻炼页面\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/building-a-workout-page/20240922-170856_hu10697918706284234294.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/building-a-workout-page/20240922-170856_hu9127555457854769002.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240922-170856.png\" width=\"2400\" height=\"1120\" alt=\"workout page\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e整个流程:\u003c/p\u003e\n\u003cp\u003e使用\u003ccode\u003eApple Watch\u003c/code\u003e记录运动,导入到\u003ccode\u003eStrava\u003c/code\u003e应用里,在通过\u003ccode\u003eworkouts_page\u003c/code\u003e工作流拉取数据构建页面。\u003c/p\u003e\n\u003cp\u003e部署过程中,顺带发现个问题,提了个PR。\u003c/p\u003e\n\u003cp\u003e这里还有个小插曲,没搞明白PR的流程,在未合入前又提交了其他代码,只好重新提了一个PR。😂\u003c/p\u003e\n","date_published":"2024-09-22T16:57:38+08:00","tags":[]},{"title":"你好 Follow","id":"https://liudon.com/posts/hi-follow/","url":"https://liudon.com/posts/hi-follow/","summary":"\u003cblockquote\u003e\n\u003cp\u003eFollow: Next generation information browser.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e最近博客圈开始流行\u003ccode\u003eFollow邀请码\u003c/code\u003e,大家各种求码,一码难求。\u003c/p\u003e\n\u003cp\u003e蹲在\u003ccode\u003eDiscord\u003c/code\u003e群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003eFollow: Next generation information browser.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e最近博客圈开始流行\u003ccode\u003eFollow邀请码\u003c/code\u003e,大家各种求码,一码难求。\u003c/p\u003e\n\u003cp\u003e蹲在\u003ccode\u003eDiscord\u003c/code\u003e群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。\u003c/p\u003e\n\u003cp\u003e上周五好不容易加上管理员,要到了一枚邀请码,终于可以激活体验了。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eFollow\u003c/code\u003e里,订阅变的异常简单,输入url,它会自己检查rss订阅。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/hi-follow/20240917-010236_hu1994357333343083286.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/hi-follow/20240917-010236_hu2418251097890743445.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240917-010236.png\" width=\"1426\" height=\"658\" alt=\"Follow\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e另外发现我的博客,在\u003ccode\u003eFollow\u003c/code\u003e显示的内容不全。\u003c/p\u003e\n\u003cp\u003e检查了一下,发现是输出的RSS内容不全。\u003c/p\u003e\n\u003cp\u003e修改\u003ccode\u003ehugo\u003c/code\u003e配置文件,开启RSS输出全文。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eShowFullTextinRSS: true\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e从木木大佬那里看到,可以认证自己的Feed,我也来搞一下我的。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eThis message is used to verify that this feed (feedId:55815884011044914) belongs to me (userId:56204227179125760). \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eJoin me in enjoying the next generation information browser https://follow.is.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e咱也是带标的了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/hi-follow/20240917-012132_hu5078466684681765960.webp 512w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/hi-follow/20240917-012132_hu3857577152063773852.png 512w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240917-012132.png\" width=\"512\" height=\"290\" alt=\"认证\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2024-09-17T00:53:38+08:00","tags":["follow"]},{"title":"中秋爬山","id":"https://liudon.com/posts/mid-autumn-festival-climb/","url":"https://liudon.com/posts/mid-autumn-festival-climb/","summary":"\u003cp\u003e中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。\u003c/p\u003e\n\u003cp\u003e晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。\u003c/p\u003e","content_html":"\u003cp\u003e中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。\u003c/p\u003e\n\u003cp\u003e晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。\u003c/p\u003e\n\u003cp\u003e园博园开了灯会,微博上看说是人巨多,还是放弃吧。\u003c/p\u003e\n\u003cp\u003e跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。\u003c/p\u003e\n\u003cp\u003e第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。\u003c/p\u003e\n\u003cp\u003e还好不远,山也不高,我们也不着急,就当遛弯。\u003c/p\u003e\n\u003cp\u003e来了好几次了,进园就直奔主题:爬。\u003c/p\u003e\n\u003cp\u003e这次我俩先走了一段山路,虽然是台阶,但是确实快。\u003c/p\u003e\n\u003cp\u003e昨天玩的太累,我们商量着还是继续走坡道吧。\u003c/p\u003e\n\u003cp\u003e花了40分钟左右登顶,最快的一次记录了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG19_hu558395342516107522.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG19_hu1966869798560919248.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG19.jpg\" width=\"1280\" height=\"1707\" alt=\"登顶\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG20_hu11028198348033128639.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG20_hu8922872351200138014.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG20.jpg\" width=\"1707\" height=\"1280\" alt=\"小憩\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e今天天气一般,能见度不高,远处都是灰蒙蒙的。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG21_hu4167798503274414761.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG21_hu13792731692067208963.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG21.jpg\" width=\"1280\" height=\"1707\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e歇到1点多,我俩开始下山。\u003c/p\u003e\n\u003cp\u003e之前在微博看到陈晓卿分享的一家新疆馆子\u003ca href=\"https://weibo.com/1647375747/OshW9uUIS\"\u003e白钻美食\u003c/a\u003e,决定晚上带娃去尝尝。\u003c/p\u003e\n\u003cp\u003e坐了1个半小时的地铁,到了吕营大街。\u003c/p\u003e\n\u003cp\u003e高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。\u003c/p\u003e\n\u003cp\u003e我俩傻乎乎的爬楼上来,累个够呛。\u003c/p\u003e\n\u003cp\u003e建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。\u003c/p\u003e\n\u003cp\u003e4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。\u003c/p\u003e\n\u003cp\u003e馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂\u003c/p\u003e\n\u003cp\u003e不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG22_hu9904456584325224195.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG22_hu12703790222321786933.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG22.jpg\" width=\"1280\" height=\"959\" alt=\"羊腿抓饭\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG23_hu8439687957801705819.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG23_hu10384701062457344398.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG23.jpg\" width=\"1280\" height=\"1707\" alt=\"羊肉串\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e然后又点了一份过油拌面,面条非常劲道,味道非常棒。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG24_hu15078672566446588769.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG24_hu12278615535880005190.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG24.jpg\" width=\"1280\" height=\"1707\" alt=\"过油拌面\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG25_hu11001828482871175540.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG25_hu5030880584826212347.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG25.jpg\" width=\"1707\" height=\"1280\" alt=\"砖茶\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e店不大,但是味道挺好,推荐去试试,就是有点远。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG26_hu5173877404146564428.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG26_hu15654812133648577655.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG26.jpg\" width=\"1280\" height=\"1707\" alt=\"白钻美食\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。\u003c/p\u003e\n\u003cp\u003e今天是暴走的一天。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG27_hu13786572578730596640.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/mid-autumn-festival-climb/WechatIMG27_hu17834791688016845712.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG27.jpg\" width=\"1280\" height=\"2774\" alt=\"健身记录\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2024-09-16T23:56:16+08:00","tags":["爬山","遛娃"]},{"title":"Google Adsense的审核之旅","id":"https://liudon.com/posts/my-google-adsense-approval-journey/","url":"https://liudon.com/posts/my-google-adsense-approval-journey/","summary":"\u003cp\u003e中午的时候,突然收到一条消息,打开一看,提示我的\u003ccode\u003eGoogle Adsense\u003c/code\u003e审核通过了。\u003c/p\u003e\n\u003cp\u003e偶然发现\u003ccode\u003eGoogle Adsense\u003c/code\u003e里居然有40美金,想起来是之前\u003ca href=\"https://liudon.org\"\u003e老博客\u003c/a\u003e加的广告。\u003c/p\u003e","content_html":"\u003cp\u003e中午的时候,突然收到一条消息,打开一看,提示我的\u003ccode\u003eGoogle Adsense\u003c/code\u003e审核通过了。\u003c/p\u003e\n\u003cp\u003e偶然发现\u003ccode\u003eGoogle Adsense\u003c/code\u003e里居然有40美金,想起来是之前\u003ca href=\"https://liudon.org\"\u003e老博客\u003c/a\u003e加的广告。\u003c/p\u003e\n\u003cp\u003e看着\u003ca href=\"https://liudon.com\"\u003e新博客\u003c/a\u003e每天也有了一些访问,打算申请\u003ccode\u003eGoogle Adsense\u003c/code\u003e,补充些维护成本。\u003c/p\u003e\n\u003cp\u003e按之前的流程搞了一遍,提交了申请。\u003c/p\u003e\n\u003cp\u003e结果过了1周多,收到审核不通过邮件,说是不符合规范:低质内容,质量不高。\u003c/p\u003e\n\u003cp\u003e搜了一下,说是现在新网站审核门槛高了。\u003c/p\u003e\n\u003cp\u003e不放弃,继续申请呗。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/my-google-adsense-approval-journey/20240916-233753_hu15305536531567989767.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/my-google-adsense-approval-journey/20240916-233753_hu9972045121987141792.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240916-233753.png\" width=\"1796\" height=\"578\" alt=\"approval google adsense\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e从3月份开始,申请了7次,全部被拒。\u003c/p\u003e\n\u003cp\u003e尤其是8月25日被拒后,提示我审核次数过多,必须得等到8月31日以后才能再次申请。\u003c/p\u003e\n\u003cp\u003e上社区发了\u003ca href=\"https://support.google.com/adsense/thread/292831058/%E7%BD%91%E7%AB%99%E5%AE%A1%E6%A0%B8%E4%B8%8D%E9%80%9A%E8%BF%87%EF%BC%8C%E5%B8%AE%E7%9C%8B%E4%B8%8B%E5%8E%9F%E5%9B%A0?hl=zh-Hans\u0026amp;sjid=5393313994536777596-AP\"\u003e帖子\u003c/a\u003e,咨询到底是什么原因,结果也没收到答复。\u003c/p\u003e\n\u003cp\u003e这个时候,就已经有点心灰意冷,想要放弃了。\u003c/p\u003e\n\u003cp\u003e9月5日的时候,想着再最后申请一把试试看,再不通过就算了。\u003c/p\u003e\n\u003cp\u003e等了1周多,感觉这次估计又悬了,已经放弃了,结果今天竟然审核通过了。\u003c/p\u003e\n\u003cp\u003e历经了8次申请,耗时半年,终于申请下来了,算是这段时间难得的一件好事。\u003c/p\u003e\n","date_published":"2024-09-16T23:18:50+08:00","tags":["google adsense"]},{"title":"让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务","id":"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/","url":"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/","summary":"\u003cp\u003e本文会介绍如何接入\u003ccode\u003efilebase\u003c/code\u003e的Names(IPNS)服务,使你的\u003ccode\u003eIPFS\u003c/code\u003e站点持久在线。\u003c/p\u003e\n\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e周末更新博客时,发现workflow的上传IPFS任务执行失败了。\u003c/p\u003e","content_html":"\u003cp\u003e本文会介绍如何接入\u003ccode\u003efilebase\u003c/code\u003e的Names(IPNS)服务,使你的\u003ccode\u003eIPFS\u003c/code\u003e站点持久在线。\u003c/p\u003e\n\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e周末更新博客时,发现workflow的上传IPFS任务执行失败了。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRun aquiladev/ipfs-action@master\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eError: RequestInit: duplex option is required when sending a body.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enode:internal/deps/undici/undici:12502\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Error.captureStackTrace(err, this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ^\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTypeError: RequestInit: duplex option is required when sending a body.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e at node:internal/deps/undici/undici:12502:13\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eNode.js v20.13.1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e查了一下,应该是\u003ccode\u003eGithub\u003c/code\u003e更新了NodeJS版本导致的。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eThe following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e研究了一下,问题在于\u003ca href=\"https://github.com/ipfs/js-ipfs\"\u003ejs-ipfs\u003c/a\u003e包的\u003ccode\u003efetch\u003c/code\u003e方法没有传\u003ccode\u003eduplex\u003c/code\u003e参数导致。\u003c/p\u003e\n\u003cp\u003e看\u003ccode\u003eGithub\u003c/code\u003e文档,官方已经不再更新了。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDEPRECATED: js-IPFS has been superseded by Helia\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e搜索一番,发现了两个包\u003ca href=\"https://github.com/ipfs/helia\"\u003ehelia\u003c/a\u003e和\u003ca href=\"https://github.com/ipfs/js-kubo-rpc-client\"\u003ejs-kubo-rpc-client\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehelia\u003c/code\u003e调用方法有\u003ca href=\"https://github.com/ipfs/helia/wiki/Migrating-from-js-IPFS\"\u003e变化\u003c/a\u003e,\u003ccode\u003ejs-kubo-rpc-client\u003c/code\u003e和原来的\u003ccode\u003ejs-ipfs\u003c/code\u003e使用一致。\u003c/p\u003e\n\u003cp\u003e捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个\u003ca href=\"https://github.com/aquiladev/ipfs-action/issues/78\"\u003eissue\u003c/a\u003e,还是作者来适配吧。\u003c/p\u003e\n\u003cp\u003e隔天看的时候,在\u003ca href=\"https://github.com/aquiladev/ipfs-action/pulls\"\u003ePull requests\u003c/a\u003e里发现已经有升级后的提交了。\u003c/p\u003e\n\u003cp\u003e哈哈,原来\u003ccode\u003efilebase\u003c/code\u003e官方早就升级适配了,\u003ca href=\"https://github.com/filebase/ipfs-action/tree/master\"\u003efilebase/ipfs-action\u003c/a\u003e,顺带发现居然还支持了\u003ccode\u003eIPNS\u003c/code\u003e更新,太完美了!!!\u003c/p\u003e\n\u003ch4 id=\"折腾记录\"\u003e折腾记录\u003c/h4\u003e\n\u003cp\u003e关于\u003ccode\u003eIPNS\u003c/code\u003e的作用,可以参考zu1k大佬的\u003ca href=\"https://zu1k.com/posts/tutorials/p2p/ipfs/\"\u003eIPFS 新手指北\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e关于\u003ccode\u003eIPFS\u003c/code\u003e的部署,可以参考我的\u003ca href=\"https://liudon.com/posts/deploy-blog-to-ipfs/\"\u003e将博客部署到星际文件系统(IPFS)\u003c/a\u003e。\u003c/p\u003e\n\u003ch5 id=\"生成密钥\"\u003e生成密钥\u003c/h5\u003e\n\u003cp\u003e因为我在云主机上部署了\u003ccode\u003eipfs\u003c/code\u003e服务,已经有在更新\u003ccode\u003eIPNS\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e这里引入\u003ccode\u003efilebase\u003c/code\u003e后,相当于多个节点来更新,需要保证\u003ccode\u003eIPNS\u003c/code\u003e地址上一致的。\u003c/p\u003e\n\u003cp\u003e所以需要将云主机的密钥导出后,导入到\u003ccode\u003efilebase\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e之前使用的是\u003ccode\u003eipfs\u003c/code\u003e默认密钥,这个是无法导出的,所以只能重新生成一个密钥,\n\u003ccode\u003eipfs-action\u003c/code\u003e为密钥名字,改成你自己的:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs key gen ipfs-action\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e注意:\u003c/strong\u003e\u003ccode\u003efilebase\u003c/code\u003e还不支持\u003ccode\u003etype/size\u003c/code\u003e参数,这里必须使用默认方式创建,否则在\u003ccode\u003efilebase\u003c/code\u003e导入已有密钥会报错。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu321491354137511694.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu14733520709407985521.png 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240904-214210.png\" width=\"800\" height=\"990\" alt=\"chat\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e查看已有密钥:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs key list -l\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e导出密钥:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs key \u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e ipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行后,当前目录下会生成一个\u003ccode\u003eipfs-action.key\u003c/code\u003e文件,内容为二进制。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003efilebase\u003c/code\u003e导入key要求为base64编码,将其转为base64编码:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecat ipfs-action.key | base64\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; 5oiR5piv5rWL6K+V\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e记住这里的base64内容,下面会用到。\u003c/p\u003e\n\u003ch5 id=\"创建name\"\u003e创建NAME\u003c/h5\u003e\n\u003cp\u003e进入\u003ca href=\"https://console.filebase.com/names\"\u003efilebase控制台\u003c/a\u003e,点击\u003ccode\u003eCreate Name\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu16390248430034468739.webp 940w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu2006163032842884511.png 940w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240904-214908.png\" width=\"940\" height=\"1348\" alt=\"input\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLabel: 备注,可以随便填\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCID: 填入IPFS的cid地址\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eName Network: 固定选IPNS\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEnabled:固定选Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eImport Existing Private Key (Optional):填入第一步的base64内容\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e确定提交。\u003c/p\u003e\n\u003ch5 id=\"修改workflow\"\u003e修改workflow\u003c/h5\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: IPFS upload to filebase\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euses: filebase\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003emaster\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewith:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e path: \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003epublic\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e service: filebase\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e pinName: ipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e filebaseBucket: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ secrets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFILEBASE_BUCKET }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e filebaseKey: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ secrets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFILEBASE_KEY }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e filebaseSecret: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ secrets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFILEBASE_SECRET }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e key: ipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e新增\u003ccode\u003ekey\u003c/code\u003e参数,值为第二步\u003ccode\u003eLabel\u003c/code\u003e填入的内容。\u003c/p\u003e\n\u003cp\u003e提交后,执行workflow,在执行结果里找到\u003ccode\u003eIPNS\u003c/code\u003e地址。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRun filebase\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003emaster\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eParsing options\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eParsed Options: {\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;path\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/home/runner/work/***.github.io/***.github.io/public\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;service\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;filebase\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;host\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ipfs.io\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5001\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;protocol\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;headers\u0026#34;\u003c/span\u003e:{},\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ipfs-action\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pinName\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ipfs-action\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pinataKey\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pinataSecret\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pinataPinName\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;filebaseBucket\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;***\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;filebaseKey\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;***\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;filebaseSecret\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;***\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;infuraProjectId\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;infuraProjectSecret\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;60000\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;verbose\u0026#34;\u003c/span\u003e:false,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pattern\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;public/**/*\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAdding files\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStarting filebase client\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStarted filebase client\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStoring files\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStored files\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUpdating name\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUpdated name\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDone\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUpload to IPFS finished successfully {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cid: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ipfs: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ipns: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch5 id=\"更新dnslink\"\u003e更新DNSlink\u003c/h5\u003e\n\u003cp\u003e更新域名的dnslink值:\u003c/p\u003e\n\u003cp\u003e普通域名\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu3229473731105826581.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu15131831393258794809.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240904-215553.png\" width=\"2174\" height=\"496\" alt=\"dns\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003eeth域名\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu3230142415535382766.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu15084167724204188645.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG16.jpg\" width=\"1280\" height=\"2774\" alt=\"eth\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e第一次和外国人在线沟通,时差原因搞了两天才把\u003ccode\u003efilebase\u003c/code\u003e导入报错的问题解决。 😂😂😂\u003c/p\u003e\n\u003cp\u003e另外吐槽一下\u003ccode\u003efilebase\u003c/code\u003e服务,sdk已经有相关实现了,文档都还没更新。\u003c/p\u003e\n","date_published":"2024-09-04T22:39:37+08:00","tags":["ipfs","filebase","github"]},{"title":"一次简短的青岛之行","id":"https://liudon.com/posts/the-trip-of-qingdao/","url":"https://liudon.com/posts/the-trip-of-qingdao/","summary":"\u003cp\u003e刚放暑假的时候,就答应了娃带她去一趟青岛。\u003c/p\u003e\n\u003cp\u003e8月份要回老家,所以定在了7月中下旬出发。\u003c/p\u003e\n\u003cp\u003e车票/酒店都订好了,结果来了个台风格美。\u003c/p\u003e\n\u003cp\u003e出发前一周一直在查天气,就怕去了一直下雨。\u003c/p\u003e","content_html":"\u003cp\u003e刚放暑假的时候,就答应了娃带她去一趟青岛。\u003c/p\u003e\n\u003cp\u003e8月份要回老家,所以定在了7月中下旬出发。\u003c/p\u003e\n\u003cp\u003e车票/酒店都订好了,结果来了个台风格美。\u003c/p\u003e\n\u003cp\u003e出发前一周一直在查天气,就怕去了一直下雨。\u003c/p\u003e\n\u003cp\u003e看了台风的预测路径,感觉可能能赶在台风来之前的空档,硬着头皮出发吧。\u003c/p\u003e\n\u003cp\u003e7月26日乘坐高铁G203,中午12点左右到达青岛。\u003c/p\u003e\n\u003cp\u003e老天很给面子,是个晴天,还有点晒。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/route_hu12609839345040660021.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/route_hu13506007279713045843.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"route.png\" width=\"2990\" height=\"1502\" alt=\"出行计划\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e本来的计划路线:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第一天:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e中午到达青岛 -\u0026gt; 天主教堂 -\u0026gt; 栈桥 -\u0026gt; 八大关 -\u0026gt; 第二海水浴场\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第二天:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e海底世界/极地海洋馆 -\u0026gt; 石老人海水浴场 -\u0026gt; 五四广场/奥帆中心夜景\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第三天:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e酒店休息返京\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e按这个路线,定了两个酒店,一个在栈桥附近,一个在海洋馆附近。\u003c/p\u003e\n\u003cp\u003e到了青岛后,先去酒店放行李,然后打车去吃饭。\u003c/p\u003e\n\u003cp\u003e青岛的第一顿饭,我们选了吃海鲜,事前查了些攻略,选择了栈桥附近的燕欣饭馆。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/yanxin_hu9902128616467469194.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/yanxin_hu1097675480764191817.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"yanxin.png\" width=\"1279\" height=\"1706\" alt=\"燕欣饭馆\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e中午很饿,上来就吃,忘记拍照了,只有吃完后的照片了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/chiwan_hu12309867523409753068.webp 960w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/chiwan_hu16201326701270414051.png 960w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"chiwan.png\" width=\"960\" height=\"720\" alt=\"一扫而光\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e油焖大虾相当不错,海肠捞饭非常好吃,韭菜很鲜。\u003c/p\u003e\n\u003cp\u003e3个人,一共花了240元,非常推荐的一家店。\u003c/p\u003e\n\u003cp\u003e吃完饭,溜达到天主教堂打卡。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/jiaotang_hu9435358433890495332.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/jiaotang_hu8489432499697884342.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"jiaotang.png\" width=\"1279\" height=\"1706\" alt=\"天主教堂\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e然后是栈桥,人非常多,中午非常晒。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/zhanqiao_hu9847400616821491205.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/zhanqiao_hu14243345525381039576.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"zhanqiao.png\" width=\"1279\" height=\"1706\" alt=\"栈桥\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e于是回酒店稍作休息,决定打车直奔第二海水浴场玩水。\u003c/p\u003e\n\u003cp\u003e踩水的感觉太好玩了,娃从一开始的有点害怕,到后面追着水玩。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/caishui_hu5514519093367087150.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/caishui_hu4959903825995376216.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"caishui.png\" width=\"1279\" height=\"1706\" alt=\"踩水\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e玩完打车去的美团推荐的双合园,地方很小,需要等位。\u003c/p\u003e\n\u003cp\u003e吃下来,感觉不如第一顿好吃,菜品一般,不太推荐。\u003c/p\u003e\n\u003cp\u003e吃完已经9点了,错过了夜景时间,直接回酒店休息了。\u003c/p\u003e\n\u003cp\u003e第二天起床,发现外面下雨了,风很大,最终没逃过台风的影响。\u003c/p\u003e\n\u003cp\u003e昨天路过海洋馆,看了外面排队的人巨多,决定不去室内这种海洋馆了。\u003c/p\u003e\n\u003cp\u003e先去了银鱼巷溜达一圈,没啥看的,中午在1907青岛老味道吃的午饭,非常不推荐的一家店。\u003c/p\u003e\n\u003cp\u003e吃完饭打车到奥帆中心,想着坐船玩一圈,到了发现因为风大停运了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/wusiguangchang_hu5127428064637612307.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/wusiguangchang_hu10967046976155803611.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"wusiguangchang.png\" width=\"1706\" height=\"1279\" alt=\"五四广场打卡\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e打卡完,直奔第三海水浴场玩水。\u003c/p\u003e\n\u003cp\u003e到了发现因为风大不让下水了,只能在沙滩上玩沙子了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu8456013446709072296.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu5258025648427076937.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"disanhaishuiyuchang.png\" width=\"1279\" height=\"1706\" alt=\"第三海水浴场\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e和娃一起抓了几只小螃蟹,虽然天气不好,娃玩的还是很开心。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/pangxie_hu14237613133558251072.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-trip-of-qingdao/pangxie_hu3787972242369278222.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"pangxie.png\" width=\"1706\" height=\"1279\" alt=\"赶海\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e实在不想吃海鲜了,晚饭在酒店附近吃了个米村拌饭,发现旁边有家类似北京的老年厨房,非常便宜,菜品也不错,第二天早晨在这里解决了。\u003c/p\u003e\n\u003cp\u003e天气不好,晚上在酒店看电视了,点了个麦当劳夜宵,结果娃没吃多少,全我吃了,给我撑的。\u003c/p\u003e\n\u003cp\u003e第三天天晴了,但是风还是大。\u003c/p\u003e\n\u003cp\u003e决定到第二海水浴场看看运气,到了之后还是不让下水,在沙滩上玩了会沙子。\u003c/p\u003e\n\u003cp\u003e时间差不多,回酒店办退房。\u003c/p\u003e\n\u003cp\u003e然后步行到火车站,上车回京。\u003c/p\u003e\n\u003cp\u003e青岛之行就此结束了,尽管行程很短,天气不太好,但一家人在一起就很开心,唯一的教训就是晚上夜宵不要吃的太多。 😂\u003c/p\u003e\n","date_published":"2024-08-31T21:24:53+08:00","tags":["旅行"]},{"title":"解决 \"undeclared name: any (requires version go1.18 or later)\" 编译错误","id":"https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/","url":"https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ protoc-gen-go --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc-gen-go v1.34.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sh make.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euser.pb.go:123:45: undeclared name: any (requires version go1.18 or later)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e流水线编译报错,其中\u003ccode\u003emake.sh\u003c/code\u003e文件代码:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego build\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。\u003c/p\u003e","content_html":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ protoc-gen-go --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc-gen-go v1.34.2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ sh make.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euser.pb.go:123:45: undeclared name: any (requires version go1.18 or later)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e流水线编译报错,其中\u003ccode\u003emake.sh\u003c/code\u003e文件代码:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego build\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。\u003c/p\u003e\n\u003cp\u003e登到流水线编译机器上,看了下\u003ccode\u003ego\u003c/code\u003e的版本已经是\u003ccode\u003e1.18.1\u003c/code\u003e了,按理不应该报这个错误的。\u003c/p\u003e\n\u003cp\u003e关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用\u003ccode\u003emaster\u003c/code\u003e分支编译了一下,也还是报这个错误。\u003c/p\u003e\n\u003cp\u003e手动执行\u003ccode\u003emake.sh\u003c/code\u003e里的每条命令,发现是\u003ccode\u003eprotoc\u003c/code\u003e编译pb文件时报的这个错误。\u003c/p\u003e\n\u003cp\u003e经过一番查找后,发现是\u003ccode\u003eprotoc-gen-go\u003c/code\u003e在4月份更新了版本,引入了新特性。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pkg.go.dev/google.golang.org/protobuf@v1.34.2/cmd/protoc-gen-go?tab=versions\"\u003eprotoc-gen-go\u0026rsquo;s versions\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eVersions in this module\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ev1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e v1.34.2 Jun 11, 2024\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e v1.34.1 May 6, 2024\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e v1.34.0 Apr 30, 2024\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e v1.33.0 Mar 5, 2024\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e v1.32.0 Dec 22, 2023\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://protobuf.dev/editions/overview/\"\u003eProtobuf Editions Overview\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eProtobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = \u0026ldquo;proto2\u0026rdquo; or syntax = \u0026ldquo;proto3\u0026rdquo; at the top of proto definition files, you use an edition number, such as edition = \u0026ldquo;2024\u0026rdquo;, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.\u003c/p\u003e\n\u003cp\u003eInstead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e改用历史版本后解决。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","date_published":"2024-06-14T20:41:20+08:00","tags":["go","protoc"]},{"title":"搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway","id":"https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/","url":"https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/","summary":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e4月底的时候,Livid大佬提醒,\u003ccode\u003eCloudflare\u003c/code\u003e应该是调整了\u003ccode\u003eIPFS Gateway\u003c/code\u003e网关策略,我的\u003ca href=\"https://liudon.xyz\"\u003eIPFS镜像博客\u003c/a\u003e无法访问了。\u003c/p\u003e\n\u003cp\u003e没查到\u003ccode\u003eCloudflare\u003c/code\u003e的调整说明,不过还好\u003ccode\u003eIPFS\u003c/code\u003e官方也提供了公共网关\u003ccode\u003egateway.ipfs.io\u003c/code\u003e,将域名解析改到官网网关。\u003c/p\u003e","content_html":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e4月底的时候,Livid大佬提醒,\u003ccode\u003eCloudflare\u003c/code\u003e应该是调整了\u003ccode\u003eIPFS Gateway\u003c/code\u003e网关策略,我的\u003ca href=\"https://liudon.xyz\"\u003eIPFS镜像博客\u003c/a\u003e无法访问了。\u003c/p\u003e\n\u003cp\u003e没查到\u003ccode\u003eCloudflare\u003c/code\u003e的调整说明,不过还好\u003ccode\u003eIPFS\u003c/code\u003e官方也提供了公共网关\u003ccode\u003egateway.ipfs.io\u003c/code\u003e,将域名解析改到官网网关。\u003c/p\u003e\n\u003cp\u003e但还是无法访问,被\u003ccode\u003eCloudflare\u003c/code\u003e拦截了。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eError 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCNAME Cross-User Banned\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWhat happened?\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eYou\u0026#39;ve requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare\u0026#39;s security policy.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWhat can I do?\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eIf this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2\u0026#39;s documentation for details.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eVisit our website to learn more about Cloudflare.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这周在Discord群里,看到有人发消息,说是\u003ccode\u003eCloudflare\u003c/code\u003e将下线\u003ccode\u003eIPFS Gateway\u003c/code\u003e网关服务。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard\"\u003ehttps://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAll traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch4 id=\"方案调研\"\u003e方案调研\u003c/h4\u003e\n\u003cp\u003e经过一番搜索,找到了一篇\u003ca href=\"https://railway.app/template/PhPjgz\"\u003e自建IPFS Gateway网关的资料\u003c/a\u003e,里面用到了\u003ca href=\"https://github.com/ipfs/bifrost-gateway\"\u003ebifrost-gateway\u003c/a\u003e组件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTo run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ PROXY_GATEWAY_URL=\u0026#34;http://127.0.0.1:8080\u0026#34; ./bifrost-gateway\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e看文档,可以通过这个命令搭建一个自己的网关服务,同时支持\u003ccode\u003eDNSLink\u003c/code\u003e方式访问。\u003c/p\u003e\n\u003cp\u003e太棒了,感觉可以自己搭一套网关,然后用\u003ccode\u003eNginx\u003c/code\u003e反代对外提供服务。\u003c/p\u003e\n\u003cp\u003e在之前\u003ca href=\"https://liudon.com/posts/deploy-blog-to-ipfs/\"\u003e将博客部署到星际文件系统(IPFS)\u003c/a\u003e文章中,已经通过\u003ccode\u003eKubo\u003c/code\u003e搭建了一套本地\u003ccode\u003eIPFS\u003c/code\u003e服务。\u003c/p\u003e\n\u003cp\u003e上机器验证一下可行性:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e启动Bifrost Gateway,网关默认地址为\u003ccode\u003ehttps://127.0.0.1:8081\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ PROXY_GATEWAY_URL=\u0026#34;http://127.0.0.1:8080\u0026#34; ./bifrost-gateway\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Starting bifrost-gateway dev-build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 GRAPH_BACKEND: false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e在另外一个终端下,执行命令\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ curl \u0026#39;http://127.0.0.1:8081/\u0026#39; -H\u0026#34;Host:liudon.xyz\u0026#34; -I\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccept-Ranges: bytes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: Content-Type\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: Range\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: User-Agent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: X-Requested-With\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Methods: GET\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Methods: HEAD\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Methods: OPTIONS\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Origin: *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: Content-Length\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: Content-Range\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Chunked-Output\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Ipfs-Path\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Ipfs-Roots\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Stream-Output\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 26283\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEtag: \u0026#34;QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLast-Modified: Wed, 22 May 2024 12:57:29 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eX-Ipfs-Path: /ipns/liudon.xyz/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eX-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Wed, 22 May 2024 12:57:29 GMT\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e验证可行,不过我记得\u003ccode\u003eKubo\u003c/code\u003e默认就有网关服务的,试一下直接通过\u003ccode\u003eKubo\u003c/code\u003e默认网关的情况。\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eKubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用\u003c/em\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ curl \u0026#39;http://127.0.0.1:8080/\u0026#39; -H\u0026#34;Host:liudon.xyz\u0026#34; -I\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccept-Ranges: bytes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: Content-Type\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: Range\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: User-Agent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Headers: X-Requested-With\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Methods: GET\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Origin: *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: Content-Length\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: Content-Range\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Chunked-Output\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Ipfs-Path\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Ipfs-Roots\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Expose-Headers: X-Stream-Output\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 26283\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eEtag: \u0026#34;QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLast-Modified: Wed, 22 May 2024 12:59:25 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eX-Ipfs-Path: /ipns/liudon.xyz/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eX-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Wed, 22 May 2024 12:59:25 GMT\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e也是可以的,那就没必要多搞一套bifrost网关了。\u003c/p\u003e\n\u003ch4 id=\"具体实现\"\u003e具体实现\u003c/h4\u003e\n\u003cp\u003e通过\u003ccode\u003eNginx\u003c/code\u003e反向代理转发到本地\u003ccode\u003eIPFS\u003c/code\u003e网关,只需要改一下解析就可以继续使用\u003ccode\u003eIPFS\u003c/code\u003e服务了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu3099176474927034.webp 904w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu9494551717517101117.png 904w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240522210154.png\" width=\"904\" height=\"1040\" alt=\"方案\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003eNginx\u003c/code\u003e反向代理\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eserver {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e listen \u003cspan style=\"color:#ae81ff\"\u003e443\u003c/span\u003e ssl http2;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e server_name liudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_certificate \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eetc\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003essl\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003efullchain\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecer;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_certificate_key \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eetc\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003essl\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ekey;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_protocols TLSv1\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e TLSv1\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_ciphers \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_prefer_server_ciphers on;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_session_cache shared:SSL:\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003em;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssl_session_timeout \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003em;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e location \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e proxy_pass http:\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e127.0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0.1\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e8080\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e proxy_set_header Host \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehost; \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注意这里要传递反代的域名信息,限制只能访问我们自己\u003c/span\u003ednslink对应的资源\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e access_log \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elog\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaccess\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elog;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e error_log \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003elog\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eerror\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elog;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e申请\u003ccode\u003eLet's Encrypt\u003c/code\u003e证书,证书相关的就不多做介绍了,网上资料很多。\u003c/p\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e更改DNS解析\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e原有的解析\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e类型:CNAME\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e名称:liudon.xyz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e内容:cloudflare-ipfs.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e新的解析\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e类型:A\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e名称:liudon.xyz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e内容:你的服务器公网IP\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e搞定,又可以继续白嫖\u003ccode\u003eIPFS\u003c/code\u003e服务了。\u003c/p\u003e\n","date_published":"2024-05-22T21:20:20+08:00","tags":["cloudflare","ipfs"]},{"title":"302跳转的跨域问题(CORS)","id":"https://liudon.com/posts/the-cors-issue-with-302-redirect/","url":"https://liudon.com/posts/the-cors-issue-with-302-redirect/","summary":"\u003cp\u003e302跳转的跨域问题\u003c/p\u003e\n\u003ch4 id=\"场景一302不返回跨域头\"\u003e场景一:302不返回跨域头\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e请求\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGET /302 HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHost: liudon.xyz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: https://www.baidu.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e返回\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCf-Ray: 88535773eaf5107e-HKG\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 143\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Fri, 17 May 2024 11:42:00 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExpires: Thu, 01 Jan 1970 00:00:01 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLocation: https://liudon.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eServer: cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eVary: Accept-Encoding\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e浏览器报错\u003c/strong\u003e\u003c/p\u003e","content_html":"\u003cp\u003e302跳转的跨域问题\u003c/p\u003e\n\u003ch4 id=\"场景一302不返回跨域头\"\u003e场景一:302不返回跨域头\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e请求\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGET /302 HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHost: liudon.xyz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: https://www.baidu.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e返回\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCf-Ray: 88535773eaf5107e-HKG\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 143\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Fri, 17 May 2024 11:42:00 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExpires: Thu, 01 Jan 1970 00:00:01 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLocation: https://liudon.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eServer: cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eVary: Accept-Encoding\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e浏览器报错\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess to fetch at \u0026#39;https://liudon.xyz/302\u0026#39; from origin \u0026#39;https://www.baidu.com\u0026#39; has been blocked by CORS policy: No \u0026#39;Access-Control-Allow-Origin\u0026#39; header is present on the requested resource. If an opaque response serves your needs, set the request\u0026#39;s mode to \u0026#39;no-cors\u0026#39; to fetch the resource with CORS disabled.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/302_hu13914265401418104620.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/302_hu12483722819857021138.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"302.png\" width=\"1530\" height=\"176\" alt=\"302\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"场景二302跳转返回跨域头\"\u003e场景二:302跳转返回跨域头\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e请求\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGET /302_return_origin_header HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHost: liudon.xyz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: https://www.baidu.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e返回\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess-Control-Allow-Origin: https://www.baidu.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCf-Ray: 88535773eaf5107e-HKG\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 143\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Fri, 17 May 2024 11:42:00 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExpires: Thu, 01 Jan 1970 00:00:01 GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLocation: https://liudon.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eServer: cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eVary: Accept-Encoding\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e浏览器报错\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccess to fetch at \u0026#39;https://liudon.org/\u0026#39; (redirected from \u0026#39;https://liudon.xyz/302_return_origin_header\u0026#39;) from origin \u0026#39;https://www.baidu.com\u0026#39; has been blocked by CORS policy: No \u0026#39;Access-Control-Allow-Origin\u0026#39; header is present on the requested resource. If an opaque response serves your needs, set the request\u0026#39;s mode to \u0026#39;no-cors\u0026#39; to fetch the resource with CORS disabled.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu13484935516549152346.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu316862039115063263.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"302_return_origin_header.png\" width=\"1536\" height=\"100\" alt=\"302 return origin header\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e注意,这里302跳转请求没有报错,是跳转后的连接报了跨域错误。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLocation请求\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGET / HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHost: liudon.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReferer: https://www.baidu.com/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/location_hu9849758582764196190.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/the-cors-issue-with-302-redirect/location_hu16254348894024931263.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"location.png\" width=\"1528\" height=\"1198\" alt=\"location\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e302返回了跨域头,所以浏览器请求了Location地址。\u003c/p\u003e\n\u003cp\u003e但为什么两次请求header头里的\u003ccode\u003eOrigin\u003c/code\u003e字段值不一致呢?第二次Location请求为什么\u003ccode\u003eOrigin\u003c/code\u003e字段值是null?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第一次:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: https://www.baidu.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第二次\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eOrigin: null\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e经过一番搜索,终于找到了一些资料。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe Origin header value may be null in a number of cases, including (non-exhaustively):\u003c/p\u003e\n\u003cp\u003eOrigins whose scheme is not one of http, https, ftp, ws, wss, or gopher (including blob, file and data).\nCross-origin images and media data, including that in \u003c!-- raw HTML omitted --\u003e, \u003c!-- raw HTML omitted --\u003e and \u003c!-- raw HTML omitted --\u003e elements.\nDocuments created programmatically using createDocument(), generated from a data: URL, or that do not have a creator browsing context.\nRedirects across origins.\niframes with a sandbox attribute that doesn\u0026rsquo;t contain the value allow-same-origin.\nResponses that are network errors.\nReferrer-Policy set to no-referrer for non-cors request modes (e.g. simple form posts).\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e出自 \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description\"\u003ehttps://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA request request has a redirect-tainted origin if these steps return true:\u003c/p\u003e\n\u003cp\u003eLet lastURL be null.\u003c/p\u003e\n\u003cp\u003eFor each url of request’s URL list:\u003c/p\u003e\n\u003cp\u003eIf lastURL is null, then set lastURL to url and continue.\u003c/p\u003e\n\u003cp\u003eIf url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true.\u003c/p\u003e\n\u003cp\u003eSet lastURL to url.\nReturn false.\nSerializing a request origin, given a request request, is to run these steps:\u003c/p\u003e\n\u003cp\u003eIf request has a redirect-tainted origin, then return \u0026ldquo;null\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eReturn request’s origin, serialized.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e出自 \u003ca href=\"https://fetch.spec.whatwg.org/#concept-request-tainted-origin\"\u003ehttps://fetch.spec.whatwg.org/#concept-request-tainted-origin\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e简单说就是如果302跳转的域与上一次请求域不同的话,就会将Origin设置为\u003ccode\u003enull\u003c/code\u003e。\u003c/p\u003e\n","date_published":"2024-05-17T20:13:57+08:00","tags":["cors"]},{"title":"GORM增加sqlcommenter特性","id":"https://liudon.com/posts/gorm-supports-sqlcommenter/","url":"https://liudon.com/posts/gorm-supports-sqlcommenter/","summary":"\u003cp\u003e什么是sqlcommenter?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003esqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.\u003c/p\u003e","content_html":"\u003cp\u003e什么是sqlcommenter?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003esqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003ccode\u003eGORM\u003c/code\u003e提供了\u003ca href=\"https://github.com/go-gorm/hints\"\u003ehints组件\u003c/a\u003e,可以支持\u003ccode\u003esqlcommenter\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimport \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/hints\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDB.\u003cspan style=\"color:#a6e22e\"\u003eClauses\u003c/span\u003e(hints.\u003cspan style=\"color:#a6e22e\"\u003eComment\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;select\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;master\u0026#34;\u003c/span\u003e)).\u003cspan style=\"color:#a6e22e\"\u003eFind\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{}\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSELECT\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e/*master*/\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFROM\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003eusers\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDB.\u003cspan style=\"color:#a6e22e\"\u003eClauses\u003c/span\u003e(hints.\u003cspan style=\"color:#a6e22e\"\u003eCommentBefore\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;insert\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;node2\u0026#34;\u003c/span\u003e)).\u003cspan style=\"color:#66d9ef\"\u003eCreate\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euser\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e/*node2*/\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eINSERT\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eINTO\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003eusers\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e ...;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDB.\u003cspan style=\"color:#a6e22e\"\u003eClauses\u003c/span\u003e(hints.\u003cspan style=\"color:#a6e22e\"\u003eCommentAfter\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;select\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;node2\u0026#34;\u003c/span\u003e)).\u003cspan style=\"color:#66d9ef\"\u003eCreate\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euser\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e/*node2*/\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eINSERT\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eINTO\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003eusers\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e ...;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDB.\u003cspan style=\"color:#a6e22e\"\u003eClauses\u003c/span\u003e(hints.\u003cspan style=\"color:#a6e22e\"\u003eCommentAfter\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;where\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hint\u0026#34;\u003c/span\u003e)).\u003cspan style=\"color:#a6e22e\"\u003eFind\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eUser\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{}\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;id = ?\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSELECT\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eFROM\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003eusers\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eWHERE\u003c/span\u003e id \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e/* hint */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e但是需要在每个执行语句里引入类似\u003ccode\u003e.Clauses(hints.CommentBefore(\u0026quot;insert\u0026quot;, \u0026quot;node2\u0026quot;))\u003c/code\u003e代码。\u003c/p\u003e\n\u003cp\u003e我希望是全局增加\u003ccode\u003esqlcommenter\u003c/code\u003e,业务侧不需要过多调整。\u003c/p\u003e\n\u003cp\u003e完整代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eplugins\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ego\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epackage plugins\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimport (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tgorm \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/gorm\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tgormclause \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/gorm/clause\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Comment struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tContent string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (c Comment) Name() string {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;COMMENT\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (c Comment) Build(builder gormclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuilder) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/* \u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContent)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; */\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (c Comment) MergeClause(mergeClause \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egormclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClause) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (c Comment) ModifyStatement(stmt \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tclause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses[c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName()]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注意这里一定要是\u003c/span\u003eExpression\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,因为\u003c/span\u003eExpression为nil的话\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,是不会触发\u003c/span\u003eBuild方法执行的\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e这里一开始参考\u003c/span\u003ehints注册的BeforeExpression\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,导致\u003c/span\u003eBuild未执行\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,直到把整个\u003c/span\u003egorm流程梳理一遍才发现问题所在\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses[c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName()] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e extraClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;COMMENT\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype CommentClausePlugin struct{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e NewCommentClausePlugin create a new ExtraPlugin\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e NewCommentClausePlugin() \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eCommentClausePlugin {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eCommentClausePlugin{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Name plugin name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (ep \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eCommentClausePlugin) Name() string {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Initialize register BuildClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (ep \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eCommentClausePlugin) Initialize(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) (err error) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tinitClauses(db)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:create\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDelete()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:delete\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQuery()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:query\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:update\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRaw()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:raw\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRow()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBefore(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:row\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CommentClausePlugin\u0026#34;\u003c/span\u003e, AddAnnotation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e AddAnnotation(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eError \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trid :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xx\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e从\u003c/span\u003econtext上下文里取rid信息\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e v, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContext\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eValue(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rid\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(string); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\trid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcontent :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e fmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rid=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, rid)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLen() \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\toldSQL :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReset()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(fmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, content, oldSQL))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(Comment{Content: content})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e initClauses init SQL clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e initClauses(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eError \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDelete()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQuery()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trawClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRaw()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trowClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(extraClause, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRow()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e createClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDelete()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e deleteClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQuery()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e queryClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdate()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e updateClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRaw()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e rawClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRow()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e rowClause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emain\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ego\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epackage main\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimport (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;context\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;plugins\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gorm \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/gorm\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/google/uuid\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Product struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Code string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Price uint\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e main() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e dsn :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(mysql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(dsn), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUse(plugins\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNewCommentClausePlugin())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eProduct{Code: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;D42\u0026#34;\u003c/span\u003e, Price: \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e传入\u003c/span\u003econtext\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,指定\u003c/span\u003erid\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ctx :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWithValue(context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBackground(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rid\u0026#34;\u003c/span\u003e, uuid\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNew()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWithContext(ctx)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eProduct{Code: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;D42\u0026#34;\u003c/span\u003e, Price: \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e阻塞了两天的问题,终于解决了!😁😁😁\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/how-gorm-generates-sql/\"\u003ehow gorm generates sql\u003c/a\u003e\u003c/p\u003e\n","date_published":"2024-04-18T21:25:24+08:00","tags":["gorm","sqlcommenter"]},{"title":"源码分析:GORM是如何生成sql的","id":"https://liudon.com/posts/how-gorm-generates-sql/","url":"https://liudon.com/posts/how-gorm-generates-sql/","summary":"\u003cp\u003e在\u003ccode\u003egorm\u003c/code\u003e下实现\u003ca href=\"https://google.github.io/sqlcommenter/\"\u003esqlcommenter\u003c/a\u003e过程中,遇到一些问题,顺便把\u003ccode\u003egorm\u003c/code\u003e整个流程梳理了一遍,整理记录一下。\u003c/p\u003e\n\u003cp\u003egorm使用示例\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epackage main\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimport (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/driver/mysql\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/gorm\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Product struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Code string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Price uint\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e main() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e参考\u003c/span\u003e https:\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ego\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esql\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edriver\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003emysql\u003cspan style=\"color:#75715e\"\u003e#dsn-data-source-name 获取详情\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e dsn :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(mysql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(dsn), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e product Product\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFirst(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eproduct, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e根据整型主键查找\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我们以\u003ccode\u003eFirst\u003c/code\u003e查询为例,看一下是怎么转成具体sql的。\u003c/p\u003e","content_html":"\u003cp\u003e在\u003ccode\u003egorm\u003c/code\u003e下实现\u003ca href=\"https://google.github.io/sqlcommenter/\"\u003esqlcommenter\u003c/a\u003e过程中,遇到一些问题,顺便把\u003ccode\u003egorm\u003c/code\u003e整个流程梳理了一遍,整理记录一下。\u003c/p\u003e\n\u003cp\u003egorm使用示例\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epackage main\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimport (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/driver/mysql\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm.io/gorm\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Product struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Code string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Price uint\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e main() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e参考\u003c/span\u003e https:\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ego\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esql\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edriver\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003emysql\u003cspan style=\"color:#75715e\"\u003e#dsn-data-source-name 获取详情\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e dsn :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(mysql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(dsn), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e product Product\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFirst(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eproduct, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e根据整型主键查找\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我们以\u003ccode\u003eFirst\u003c/code\u003e查询为例,看一下是怎么转成具体sql的。\u003c/p\u003e\n\u003cp\u003e在\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/finisher_api.go\"\u003efinisher_api.go\u003c/a\u003e文件,声明了\u003ccode\u003eFirst\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e First finds the first record ordered by primary key, matching given conditions conds\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB) First(dest interface{}, conds \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003einterface{}) (tx \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注册\u003c/span\u003eOrder类型的Clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\ttx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLimit(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOrder(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOrderByColumn{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tColumn: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCurrentTable, Name: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryKey},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e这里如果有指定条件的话,注册一个\u003c/span\u003eWhere类型的Clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(conds) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e exprs :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildCondition(conds[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e], conds[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:]\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e); len(exprs) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\ttx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWhere{Exprs: exprs})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\ttx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRaiseErrorOnNotFound \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\ttx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e dest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e tx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecallbacks\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQuery()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExecute(tx)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/gorm.go\"\u003egorm.go\u003c/a\u003e文件,可以找到\u003ccode\u003etx.callbacks\u003c/code\u003e定义。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Config struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcallbacks *callbacks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcacheStore *sync.Map\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/callbacks.go\"\u003ecallbacks.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e callbacks gorm callbacks manager\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype callbacks struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tprocessors map[string]\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eprocessor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype processor struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdb \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tClauses []string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tfns []\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcallbacks []\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003ecallback\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype callback struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tname string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tbefore string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tafter string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tremove \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\treplace \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tmatch \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB) \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\thandler \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tprocessor \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eprocessor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e返回\u003c/span\u003equery类型的processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (cs \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003ecallbacks) Query() \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eprocessor {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e cs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eprocessors[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;query\u0026#34;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (p \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eprocessor) Execute(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB) \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eDB {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e call scopes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escopes) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexecuteScopes()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tcurTime \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e time\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNow()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tresetBuildClauses \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注意这里的\u003c/span\u003estmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,后面会用到这个信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tresetBuildClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e optimizer, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(StatementModifier); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\toptimizer\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModifyStatement(stmt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e assign model values\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e parse model values\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParse(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModel); err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eerrors\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIs(err, schema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eErrUnsupportedDataType) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e (stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTableExpr \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLen() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e errors\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIs(err, schema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eErrUnsupportedDataType) \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTableExpr \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddError(fmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eErrorf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%w: Table not set, please set it like: db.Model(\u0026amp;user) or db.Table(\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eusers\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e)\u0026#34;\u003c/span\u003e, err))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddError(err)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e assign stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eValueOf(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eKind() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePtr {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsNil() \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCanAddr() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSet(reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNew(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eElem()))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eElem()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003estmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsValid() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddError(ErrInvalidValue)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e根据优先级执行不同\u003c/span\u003ecallback的回调方法\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, f :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range p\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efns {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tf(db)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLen() \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLogger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTrace(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContext, curTime, \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() (string, int64) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tsql, vars :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString(), stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e filter, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLogger\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(ParamsFilter); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tsql, vars \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e filter\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParamsFilter(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContext, stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString(), stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExplain(sql, vars\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e), db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRowsAffected\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eError)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003estmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDryRun {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReset()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nil\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e resetBuildClauses {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nil\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e db\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e接下来,我们来看一下内置的\u003ccode\u003ecallback\u003c/code\u003e是如何注册的。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/mysql/blob/master/mysql.go\"\u003emysql.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e CreateClauses create clauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tCreateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;INSERT\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;VALUES\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ON CONFLICT\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e QueryClauses query clauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tQueryClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e UpdateClauses update clauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tUpdateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;UPDATE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SET\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ORDER BY\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;LIMIT\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e DeleteClauses delete clauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tDeleteClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DELETE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FROM\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ORDER BY\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;LIMIT\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdefaultDatetimePrecision \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (dialector Dialector) Initialize(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) (err error) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDriverName \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDriverName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mysql\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDefaultDatetimePrecision \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDefaultDatetimePrecision \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003edefaultDatetimePrecision\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConn \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConnPool \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConn\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConnPool, err \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sql\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOpen(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDriverName, dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDSN)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e err\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\twithReturning :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003edialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSkipInitializeWithVersion {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\terr \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConnPool\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryRowContext(context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBackground(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SELECT VERSION()\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eScan(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003edialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e err\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContains(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;MariaDB\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameIndex \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameColumn \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportForShareClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportNullAsDefaultValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\twithReturning \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e checkVersion(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;10.5\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHasPrefix(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5.6.\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameIndex \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameColumn \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportForShareClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportDropConstraint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHasPrefix(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5.7.\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameColumn \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportForShareClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportDropConstraint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHasPrefix(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5.\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDisableDatetimePrecision \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameIndex \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameColumn \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportForShareClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportDropConstraint \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContains(dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eServerVersion, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;TiDB\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDontSupportRenameColumnUnique \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e register callbacks\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcallbackConfig :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ecallbacks\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tCreateClauses: CreateClauses,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tQueryClauses: QueryClauses,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tUpdateClauses: UpdateClauses,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tDeleteClauses: DeleteClauses,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003edialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDisableWithReturning \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e withReturning {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eutils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContains(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tcallbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eutils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContains(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tcallbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eutils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContains(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tcallbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(callbackConfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;RETURNING\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注册默认\u003c/span\u003ecallback\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcallbacks\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegisterDefaultCallbacks(db, callbackConfig)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e k, v :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range dialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauseBuilders() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauseBuilders[k] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/callbacks/callbacks.go\"\u003ecallbacks.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;INSERT\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;VALUES\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ON CONFLICT\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SELECT\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FROM\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GROUP BY\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ORDER BY\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;LIMIT\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FOR\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;UPDATE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SET\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []string{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DELETE\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FROM\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Config struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tLastInsertIDReversed \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tCreateClauses []string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tQueryClauses []string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tUpdateClauses []string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tDeleteClauses []string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e RegisterDefaultCallbacks(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB, config \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eConfig) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tenableTransaction :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003edb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSkipDefaultTransaction\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tconfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e createClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tconfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e queryClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tconfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e deleteClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tconfig\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e updateClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e注册不同类型的\u003c/span\u003ecallback\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreate()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:begin_transaction\u0026#34;\u003c/span\u003e, BeginTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:before_create\u0026#34;\u003c/span\u003e, BeforeCreate)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:save_before_associations\u0026#34;\u003c/span\u003e, SaveBeforeAssociations(true))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:create\u0026#34;\u003c/span\u003e, Create(config))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:save_after_associations\u0026#34;\u003c/span\u003e, SaveAfterAssociations(true))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:after_create\u0026#34;\u003c/span\u003e, AfterCreate)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:commit_or_rollback_transaction\u0026#34;\u003c/span\u003e, CommitOrRollbackTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tcreateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCreateClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQuery()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:query\u0026#34;\u003c/span\u003e, Query)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:preload\u0026#34;\u003c/span\u003e, Preload)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:after_query\u0026#34;\u003c/span\u003e, AfterQuery)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tqueryCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDelete()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:begin_transaction\u0026#34;\u003c/span\u003e, BeginTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:before_delete\u0026#34;\u003c/span\u003e, BeforeDelete)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:delete_before_associations\u0026#34;\u003c/span\u003e, DeleteBeforeAssociations)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:delete\u0026#34;\u003c/span\u003e, Delete(config))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:after_delete\u0026#34;\u003c/span\u003e, AfterDelete)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:commit_or_rollback_transaction\u0026#34;\u003c/span\u003e, CommitOrRollbackTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tdeleteCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDeleteClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdate()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:begin_transaction\u0026#34;\u003c/span\u003e, BeginTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:setup_reflect_value\u0026#34;\u003c/span\u003e, SetupUpdateReflectValue)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:before_update\u0026#34;\u003c/span\u003e, BeforeUpdate)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:save_before_associations\u0026#34;\u003c/span\u003e, SaveBeforeAssociations(false))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:update\u0026#34;\u003c/span\u003e, Update(config))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:save_after_associations\u0026#34;\u003c/span\u003e, SaveAfterAssociations(false))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:after_update\u0026#34;\u003c/span\u003e, AfterUpdate)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMatch(enableTransaction)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:commit_or_rollback_transaction\u0026#34;\u003c/span\u003e, CommitOrRollbackTransaction)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tupdateCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUpdateClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trowCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRow()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trowCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:row\u0026#34;\u003c/span\u003e, RowQuery)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trowCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trawCallback :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCallback()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRaw()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trawCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRegister(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gorm:raw\u0026#34;\u003c/span\u003e, RawExec)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\trawCallback\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e到这里,默认callback就注册完成了,但是是如何转成对应sql的呢?\u003c/p\u003e\n\u003cp\u003e别急,我们继续往下看。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eRegisterDefaultCallbacks\u003c/code\u003e方法里注册了一个\u003ccode\u003egorm:query\u003c/code\u003e类型的callback,对应的回调方法为\u003ccode\u003eQuery\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/callbacks/query.go\"\u003equery.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e Query(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eError \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e调用\u003c/span\u003eBuildQuerySQL方法\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tBuildQuerySQL(db)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003edb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDryRun \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eError \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\trows, err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConnPool\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryContext(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContext, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString(), db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddError(err)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdefer \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddError(rows\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClose())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tgorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eScan(rows, db, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e BuildQuerySQL(db \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003egorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, c :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(c)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLen() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGrow(\u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tclauseSelect :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelect{Distinct: db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDistinct}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eKind() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStruct \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModelType {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e conds []clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, primaryField :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryFields {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e v, isZero :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e primaryField\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eValueOf(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eContext, db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue); \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eisZero {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tconds \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(conds, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eEq{Column: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable, Name: primaryField\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName}, Value: v})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(conds) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWhere{Exprs: conds})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelects) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn, len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelects))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, name :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelects {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Name: name, Raw: true}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e f :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eLookUpField(name); f \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Name: f\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Name: name, Raw: true}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOmits) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tselectColumns, _ :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelectAndOmitColumns(false, false)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, dbName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e v, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e selectColumns[dbName]; (ok \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e v) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(clauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable, Name: dbName})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsValid() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tqueryFields :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryFields\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003equeryFields {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eswitch\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eKind() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStruct:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tqueryFields \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType() \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModelType\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e reflect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSlice:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tqueryFields \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReflectValue\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType()\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eElem() \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModelType\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e queryFields {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tstmt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement{DB: db}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e smaller struct\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e err :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParse(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDest); err \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e nil \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e (db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryFields \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModelType \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eModelType) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn, len(stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, dbName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable, Name: dbName}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e inline joins\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tfromClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFrom{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e v, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;FROM\u0026#34;\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFrom); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tfromClause \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins) \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e len(fromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins) \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelects) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOmits) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn, len(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, dbName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable, Name: dbName}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tspecifiedRelationsName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make(map[string]interface{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, join :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e isRelations \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e is relations \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e raw sql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e relations []\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eschema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationship\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\trelation, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationships\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelations[join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tisRelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\trelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(relations, relation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e handle nested join like \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Manager.Company\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tnestedJoinNames :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSplit(join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(nestedJoinNames) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tisNestedJoin :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tgussNestedRelations :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eschema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationship, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, len(nestedJoinNames))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tcurrentRelations :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationships\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, relname :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range nestedJoinNames {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e incomplete match, only treated as raw sql\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e relation, ok \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e currentRelations[relname]; ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tgussNestedRelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(gussNestedRelations, relation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tcurrentRelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFieldSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationships\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tisNestedJoin \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e isNestedJoin {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tisRelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\trelations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gussNestedRelations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e isRelations {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tgenJoinClause :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(joinType clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoinType, parentTableName string, relation \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eschema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelationship) clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoin {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\ttableAliasName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e parentTableName \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCurrentTable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\ttableAliasName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e utils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNestedRelationName(parentTableName, tableAliasName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tcolumnStmt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tTable: tableAliasName, DB: db, Schema: relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFieldSchema,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tSelects: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelects, Omits: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOmits,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tselectColumns, restricted :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e columnStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSelectAndOmitColumns(false, false)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, s :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFieldSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBNames {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e v, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e selectColumns[s]; (ok \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e v) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eok \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003erestricted) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tclauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(clauseSelect\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\tTable: tableAliasName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\tName: s,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\tAlias: utils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNestedRelationName(tableAliasName, s),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\texprs :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e make([]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression, len(relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReferences))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, ref :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReferences {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOwnPrimaryKey {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\texprs[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eEq{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\tColumn: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: parentTableName, Name: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryKey\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\tValue: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: tableAliasName, Name: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eForeignKey\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryValue \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\texprs[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eEq{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\tColumn: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: parentTableName, Name: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eForeignKey\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\tValue: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: tableAliasName, Name: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryKey\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\texprs[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eEq{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\tColumn: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumn{Table: tableAliasName, Name: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eForeignKey\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDBName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\tValue: ref\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePrimaryValue,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tonStmt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e gorm\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement{Table: tableAliasName, DB: db, Clauses: map[string]clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClause{}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, c :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFieldSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eQueryClauses {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tonStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(c)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOn \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\tonStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eOn)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e cs, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e onStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;WHERE\u0026#34;\u003c/span\u003e]; ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e where, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWhere); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\twhere\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eonStmt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e onSQL :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e onStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString(); onSQL \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\tvars :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e onStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, v :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range vars {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t\tbindvar :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuilder{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t\tonStmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eVars \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e vars[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e : idx\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDialector\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBindVarTo(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ebindvar, \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eonStmt, v)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t\tonSQL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e strings\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eReplace(onSQL, bindvar\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eString(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t\texprs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(exprs, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpr{SQL: onSQL, Vars: vars})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoin{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tType: joinType,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tTable: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable{Name: relation\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFieldSchema\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTable, Alias: tableAliasName},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tON: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWhere{Exprs: exprs},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tparentTableName :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCurrentTable\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, rel :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range relations {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e joins table alias like \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Manager, Company, Manager__Company\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tnestedAlias :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e utils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNestedRelationName(parentTableName, rel\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e _, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e specifiedRelationsName[nestedAlias]; \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tfromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(fromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins, genJoinClause(join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoinType, parentTableName, rel))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tspecifiedRelationsName[nestedAlias] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nil\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e parentTableName \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eCurrentTable {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tparentTableName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e utils\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNestedRelationName(parentTableName, rel\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t\tparentTableName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e rel\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tfromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(fromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoin{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t\tExpression: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNamedExpr{SQL: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName, Vars: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConds},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\t})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\tfromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e append(fromClause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoins, clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eJoin{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t\tExpression: clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eNamedExpr{SQL: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName, Vars: join\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eConds},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\t})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClause(fromClause)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClauseIfNotExists(clause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eFrom{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAddClauseIfNotExists(clauseSelect)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses眼熟吗\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?还记得前面的\u003c/span\u003estmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses吗\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tdb\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(db\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eStatement\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuildClauses\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e重头戏终于来了,\u003ccode\u003eQuery\u003c/code\u003e方法里调用了\u003ccode\u003eBuildQuerySQl\u003c/code\u003e,看名字也能猜到这里就是生成sql了,这里最终调用了\u003ccode\u003edb.Statement.Build\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/statement.go\"\u003estatement.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Build build sql with clauses names\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (stmt \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eStatement) Build(clauses \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003estring) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e firstClauseWritten \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e _, name :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range clauses {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauses[name]; ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e firstClauseWritten {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tstmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tfirstClauseWritten \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e b, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e stmt\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDB\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eClauseBuilders[name]; ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tb(c, stmt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(stmt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里会根据\u003ccode\u003estatement\u003c/code\u003e的\u003ccode\u003eBuildCluauses\u003c/code\u003e属性,执行\u003ccode\u003eClause\u003c/code\u003e的\u003ccode\u003eBuild\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/go-gorm/gorm/blob/master/clause/clause.go\"\u003eclause.go\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e ClauseBuilder clause builder, allows to customize how to build clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype ClauseBuilder \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(Clause, Builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Writer interface {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tWriteByte(byte) error\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tWriteString(string) (\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e, error)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Builder builder interface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Builder interface {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tWriter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tWriteQuoted(field interface{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tAddVar(Writer, \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003einterface{})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tAddError(error) error\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Clause struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tName string \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e WHERE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tBeforeExpression Expression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tAfterNameExpression Expression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tAfterExpression Expression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tExpression Expression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tBuilder ClauseBuilder\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Build build clause\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (c Clause) Build(builder Builder) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuilder \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuilder(c, builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBeforeExpression \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBeforeExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAfterNameExpression \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAfterNameExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e c\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAfterExpression \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39; \u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eAfterExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eBuild(builder)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里会执行对应Clause的Build方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Select select attrs when querying, updating, creating\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etype Select struct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tDistinct \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tColumns []Column\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tExpression Expression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (s Select) Name() string {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;SELECT\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (s Select) Build(builder Builder) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e len(s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns) \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDistinct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteString(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DISTINCT \u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e idx, column :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e range s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eColumns {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;,\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteQuoted(column)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tbuilder\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWriteByte(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (s Select) MergeClause(clause \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eClause) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e nil {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDistinct {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e expr, ok :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(Expr); ok {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\texpr\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DISTINCT \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e expr\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSQL\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\tclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e expr\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t} \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\tclause\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eExpression \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e s\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这是\u003ccode\u003eSelect\u003c/code\u003e类型的\u003ccode\u003eClause\u003c/code\u003e定义,是不是一下就清楚了。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003egorm\u003c/code\u003e通过\u003ccode\u003ecallback\u003c/code\u003e里注册\u003ccode\u003eClause\u003c/code\u003e,在\u003ccode\u003eClause\u003c/code\u003e里实现了sql拼接操作。\u003c/p\u003e\n\u003cp\u003e看了几回源码,这次总算是搞清楚了。\u003c/p\u003e\n","date_published":"2024-04-18T21:14:24+08:00","tags":["gorm"]},{"title":"工银亚洲网银密码重置","id":"https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/","url":"https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/","summary":"\u003cp\u003e18年的时候办了张工银亚洲的银行卡,好几年没有用过了。\u003c/p\u003e\n\u003cp\u003e今年想起来了,发现网银登不上了,密码忘了。\u003c/p\u003e\n\u003cp\u003e最悲剧的是,试了超过10次,账户冻结了。\u003c/p\u003e\n\u003cp\u003e打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。\u003c/p\u003e","content_html":"\u003cp\u003e18年的时候办了张工银亚洲的银行卡,好几年没有用过了。\u003c/p\u003e\n\u003cp\u003e今年想起来了,发现网银登不上了,密码忘了。\u003c/p\u003e\n\u003cp\u003e最悲剧的是,试了超过10次,账户冻结了。\u003c/p\u003e\n\u003cp\u003e打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。\u003c/p\u003e\n\u003cp\u003e1月27日,东城出入境办理港澳通行证签注,排队半个小时左右,自助机操作。\u003c/p\u003e\n\u003cp\u003e顺路跑到附近可以见证开户的工行,说需要香港那边配合,人周末不上班,只能工作日。\u003c/p\u003e\n\u003cp\u003e电话西二旗支行,说是可以早上早点来办理,要不过年人多,没时间处理。\u003c/p\u003e\n\u003cp\u003e1月30日,早上直奔银行,赶在开门前到,第一个办理。\u003c/p\u003e\n\u003cp\u003e工作人员会指导你填写资料,填完后会给你两个信封,里面是重置后的网银密码。\u003c/p\u003e\n\u003cp\u003e1月31日下午,95588打电话,我以为是广告没接。\u003c/p\u003e\n\u003cp\u003e第二天早上反应过来,在路上的时候又打过来了。\u003c/p\u003e\n\u003cp\u003e香港那边直接电话,跟你确认身份信息后,下午就收到工行发来的已更新资料邮件。\u003c/p\u003e\n\u003cp\u003e其实这里密码就重置完了,因为我同时办理了更新通信地址,我以为是只更新了地址。\u003c/p\u003e\n\u003cp\u003e至此,整个流程就办完了,还是非常方便的。\u003c/p\u003e\n\u003cp\u003e另外,发现招银亚洲也没法登陆了,U盾过期了,又跑了一趟招行,重新办了个U盾。\u003c/p\u003e\n\u003cp\u003e好了,这下两个银行都可以正常使用了。\u003c/p\u003e\n","date_published":"2024-03-16T10:09:59+08:00","tags":[]},{"title":"加速Cloudflare访问","id":"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/","url":"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/","summary":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"blog.png\" width=\"828\" height=\"753\" alt=\"博客架构\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e这是当前的博客架构,文件保存在\u003ccode\u003eGithub\u003c/code\u003e仓库,通过\u003ccode\u003eCloudflare Page\u003c/code\u003e提供访问。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"slow.png\" width=\"1842\" height=\"531\" alt=\"国内访问情况\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e众所周知,在国内,\u003ccode\u003eCloudflare\u003c/code\u003e的CDN属于反向加速,平均耗时在1.5s左右。\u003c/p\u003e","content_html":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"blog.png\" width=\"828\" height=\"753\" alt=\"博客架构\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e这是当前的博客架构,文件保存在\u003ccode\u003eGithub\u003c/code\u003e仓库,通过\u003ccode\u003eCloudflare Page\u003c/code\u003e提供访问。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"slow.png\" width=\"1842\" height=\"531\" alt=\"国内访问情况\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e众所周知,在国内,\u003ccode\u003eCloudflare\u003c/code\u003e的CDN属于反向加速,平均耗时在1.5s左右。\u003c/p\u003e\n\u003cp\u003e今天,我们就来讲一下,如何实现国内海外双线路博客访问。\u003c/p\u003e\n\u003ch4 id=\"大体思路\"\u003e大体思路\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e国内CDN添加域名后,也会提供一个cname域名B。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e使用国内dns解析服务,配置cname双线路解析。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"具体操作\"\u003e具体操作\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003eCloudflare Page\u003c/code\u003e添加新域名解析\u003c/p\u003e\n\u003cp\u003e这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/page_hu5001045409527403165.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/page_hu279659283536568196.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"page.png\" width=\"1362\" height=\"407\" alt=\"Cloudflare Page添加域名\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e配置国内CDN\u003c/p\u003e\n\u003cp\u003e我用的腾讯云,其他服务商也是可以的。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/cdn_hu7682288896726837721.webp 957w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/cdn_hu2441418303555843155.png 957w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"cdn.png\" width=\"957\" height=\"992\" alt=\"添加CDN域名\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e加速域名:填写博客对外访问的域名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e回源地址和Host:填写第一步新加的域名\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e添加成功后,会有一个cname地址,这里是国内线路解析要用到的。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDNS解析调整\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eCloudflare\u003c/code\u003e不支持双线路配置,国内服务商支持,我这里用的是腾讯云。\u003c/p\u003e\n\u003cp\u003e首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到\u003ccode\u003eCloudflare\u003c/code\u003e的邮件,不需要理会。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eliudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esandals.dnspod.net\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eheron.dnspod.net\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[not set]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[not set]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[not set]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后添加解析,默认走国内CDN,境外走\u003ccode\u003eCloudflare Page\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/dns_hu17868764939609469181.webp 1006w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/dns_hu5955705786880707343.png 1006w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"dns.png\" width=\"1006\" height=\"249\" alt=\"DNS双线路解析\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"额外的问题\"\u003e额外的问题\u003c/h4\u003e\n\u003cp\u003e为了加速\u003ccode\u003eGoogle Analytics\u003c/code\u003e,使用\u003ccode\u003eCloudflare Worker\u003c/code\u003e进行了反代,具体见\u003ca href=\"https://liudon.com/posts/optimize-google-analytics/\"\u003e加速Google Analytics\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e更改NS后,导致海外访问无法触发\u003ccode\u003eCloudflare Worker\u003c/code\u003e了,导致没有博客统计数据了。\u003c/p\u003e\n\u003cp\u003e经过一番搜索后,发现\u003ccode\u003eCloudflare Page\u003c/code\u003e有类似的\u003ccode\u003eFunction\u003c/code\u003e功能,只需要在网站根目录下新建\u003ccode\u003efunctions\u003c/code\u003e目录,添加对应文件即可。\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003eHugo\u003c/code\u003e静态博客举例说明:\u003c/p\u003e\n\u003cp\u003e在根目录的\u003ccode\u003estatic\u003c/code\u003e目录下,新建\u003ccode\u003efunctions\u003c/code\u003e目录,新建\u003ccode\u003eanalytics\u003c/code\u003e目录,添加\u003ccode\u003epost.js\u003c/code\u003e文件。\u003c/p\u003e\n\u003cp\u003e这个\u003ccode\u003eanalytics/post.js\u003c/code\u003e是为了对应原有\u003ccode\u003eWorker\u003c/code\u003e的访问地址\u003ccode\u003eanalytics/post\u003c/code\u003e,可自行修改。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003epost.js\u003c/code\u003e文件代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e async function onRequest(context) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e try {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e await postHandler(context);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } catch(e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Response(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessage}\\n\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estack}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e, { status: \u003cspan style=\"color:#ae81ff\"\u003e500\u003c/span\u003e }); \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003easync function postHandler(context) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e GA_DOMAIN \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;google-analytics.com\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e GA_COLLECT_PATH \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;g\\/collect\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e COLLECT_PATH \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;analytics/post\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e DOMAIN \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;这里填你博客的域名\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e cf_ip \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eheaders\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;CF-Connecting-IP\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e cf_country \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecf\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecountry;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e ga_url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e url\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplace(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{COLLECT_PATH}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{GA_DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{GA_COLLECT_PATH}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e newReq \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e await readRequest(context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest, ga_url);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e context\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewaitUntil(fetch(newReq));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Response(null, {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e status: \u003cspan style=\"color:#ae81ff\"\u003e204\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e statusText: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;No Content\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003easync function readRequest(request, url) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e { _, headers } \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e request;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e nq \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e method: request\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emethod,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e headers: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Origin: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;origin\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Cache-Control\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;max-age=0\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;User-Agent\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;user-agent\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Accept: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;accept\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Accept-Language\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;accept-language\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content-type\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;text/plain\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Referer: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;referer\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e body: request\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebody,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Request(url, nq);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"优化效果\"\u003e优化效果\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/result_hu5247482790724463766.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/result_hu16478955910687504533.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"result.png\" width=\"1841\" height=\"484\" alt=\"优化后的访问\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e有了国内CDN的加持,平均耗时优化到1s左右了。\u003c/p\u003e\n","date_published":"2024-02-21T20:25:49+08:00","tags":["cloudflare","博客优化"]},{"title":"2023年终总结","id":"https://liudon.com/posts/review-2023/","url":"https://liudon.com/posts/review-2023/","summary":"\u003cp\u003e2023年过完了,是时候来个总结了。\u003c/p\u003e\n\u003ch4 id=\"博客\"\u003e博客\u003c/h4\u003e\n\u003cp\u003e2023年一共更新了15篇内容,共计12000字。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240104184720_hu9917978577706645891.webp 733w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/review-2023/20240104184720_hu6551399417996237227.png 733w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240104184720.png\" width=\"733\" height=\"436\" alt=\"Google Analytics全年统计\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e访问Top3的文章:\u003c/p\u003e","content_html":"\u003cp\u003e2023年过完了,是时候来个总结了。\u003c/p\u003e\n\u003ch4 id=\"博客\"\u003e博客\u003c/h4\u003e\n\u003cp\u003e2023年一共更新了15篇内容,共计12000字。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240104184720_hu9917978577706645891.webp 733w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/review-2023/20240104184720_hu6551399417996237227.png 733w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240104184720.png\" width=\"733\" height=\"436\" alt=\"Google Analytics全年统计\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e访问Top3的文章:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/deploy-blog-to-ipfs/\"\u003e将博客部署到星际文件系统(IPFS)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/\"\u003e利用Github Actions定时抓取微博\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/fix-blog-cls/\"\u003e优化博客的累计布局偏移(CLS)问题\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e主要是因为有在v2ex发帖导流,所以访问量高一些。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/\"\u003e2023年12月北京暴雪记录\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e没想到的是一篇暴雪记录,收获了最多的评论,可能大家更容易共情。\u003c/p\u003e\n\u003cp\u003e不过从侧面也说明了技术的东西并没有太多人看,所以后来就不再分享导流了。\u003c/p\u003e\n\u003ch4 id=\"工作\"\u003e工作\u003c/h4\u003e\n\u003cp\u003e今年搬到了后厂村,见识了互联网的人流。\u003c/p\u003e\n\u003cp\u003e在23年最后一个工作日,下班路上,算了一下,这一年晚上9点半之后打车59次。\u003c/p\u003e\n\u003cp\u003e而且年底这段时间,打车愈发困难,至少要排队1个小时。\u003c/p\u003e\n\u003cp\u003e相比之前一坐坐一天,每天中午会绕公司大楼转两圈。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240104190037_hu12284403497067512678.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240104190037_hu711299581871800474.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240104190037.jpg\" width=\"1280\" height=\"1302\" alt=\"23年步行统计\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"驾照\"\u003e驾照\u003c/h4\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.com/posts/my-journey-of-learning-to-drive/\"\u003e驾照终于到手了\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e趁着娃暑假、十一假期,开车上路了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240104190649_hu6995472081980817639.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240104190649_hu15367748783548984010.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240104190649.jpg\" width=\"1280\" height=\"2276\" alt=\"第一次开车上路\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e参与了第一次摇号。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240106165345_hu16873662734277985674.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240106165345_hu10579543192642342465.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240106165345.jpg\" width=\"1280\" height=\"2774\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"换了新手机\"\u003e换了新手机\u003c/h4\u003e\n\u003cp\u003e用了5年的iPhone7 plus,过地铁NFC时不时刷不开了,感觉得换手机了。\u003c/p\u003e\n\u003cp\u003e十一假期回来在官网订了15pro,需要11月21日才能发货。\u003c/p\u003e\n\u003cp\u003e订货后,老手机的问题越来越多,换手机变的急迫起来。\u003c/p\u003e\n\u003cp\u003e于是,开始刷实体店取货。\u003c/p\u003e\n\u003cp\u003e用了探火app监控,10月11日中午抢到一台王府井取货。\u003c/p\u003e\n\u003cp\u003e10月12日晚上8点出发,上16号线地铁后,老手机开始持续发烫,过一会自动黑屏了。\u003c/p\u003e\n\u003cp\u003e按键有触感,但是屏幕不亮,怎么捣鼓也不行,手机越来越烫,都有点怕它炸了。\u003c/p\u003e\n\u003cp\u003e换乘8号线的路上,试了试按住所有按键,手机重启了,看见苹果logo那一刻真好。\u003c/p\u003e\n\u003cp\u003e重启完总算正常了,进店排队取货,提前了一个月拿到了新手机。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105132858_hu8507553718241607449.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105132858_hu16448111177314676496.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105132858.jpg\" width=\"1280\" height=\"1707\" alt=\"王府井Apple Store\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105132906_hu2599040368997226671.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105132906_hu11585877997970186359.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105132906.jpg\" width=\"1280\" height=\"1707\" alt=\"取完手机\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e升级体验非常好,使用丝滑,再也不用插着充电器玩手机了,感谢老婆。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133752_hu12313603963692183601.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133752_hu13682909496706761490.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133752.jpg\" width=\"1707\" height=\"1280\" alt=\"大风\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133758_hu11337496756744297927.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133758_hu17465420055628348745.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133758.jpg\" width=\"1707\" height=\"1280\" alt=\"公司大楼\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133747_hu17012946487418440240.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133747_hu6297396821765535546.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133747.jpg\" width=\"1280\" height=\"1707\" alt=\"枫叶\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133803_hu14918917217174035220.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133803_hu18115659086331184125.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133803.jpg\" width=\"1280\" height=\"1707\" alt=\"秋\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133808_hu8118592834658162822.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133808_hu1518807698234557636.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133808.jpg\" width=\"1280\" height=\"1707\" alt=\"冬\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133819_hu2756879506712662381.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133819_hu18219205550022003430.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133819.jpg\" width=\"1280\" height=\"1707\" alt=\"冬\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133739_hu12607025353230258707.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133739_hu5679039284297789299.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133739.jpg\" width=\"1280\" height=\"1707\" alt=\"秋天的百望山\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133814_hu7246078058378856150.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133814_hu2907940507684878440.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133814.jpg\" width=\"1280\" height=\"1707\" alt=\"冬天的百望山\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240105133825_hu4218178355415660863.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240105133825_hu13122219817455820098.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240105133825.jpg\" width=\"1280\" height=\"1707\" alt=\"冬天的百望山\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"财务\"\u003e财务\u003c/h4\u003e\n\u003cp\u003e提前还了两笔房贷,希望明年可以把商贷还完。\u003c/p\u003e\n\u003cp\u003e股市收益率-1.11%,港股套牢中,美股稍微回了点血。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2023/20240104191508_hu15087142587093087512.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2023/20240104191508_hu8976748153054708540.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20240104191508.jpg\" width=\"1290\" height=\"1122\" alt=\"股市收益\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e本命年了,希望一切顺利。\u003c/p\u003e\n\u003cp\u003e祝大家新年快乐!\u003c/p\u003e\n","date_published":"2024-01-04T18:41:20+08:00","tags":["年终总结"]},{"title":"2023年12月北京暴雪记录","id":"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/","url":"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/","summary":"\u003cblockquote\u003e\n\u003cp\u003e记录暴雪下普通打工人的生活。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch4 id=\"12月14日-周四\"\u003e12月14日 周四\u003c/h4\u003e\n\u003cp\u003e北京的雪已经连着下了两天了。\u003c/p\u003e\n\u003cp\u003e12月11日,也是因为下雪,晚上打车打到10点半才叫到车。\u003c/p\u003e\n\u003cp\u003e所以这次下雪后,晚上就早走了。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003e记录暴雪下普通打工人的生活。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch4 id=\"12月14日-周四\"\u003e12月14日 周四\u003c/h4\u003e\n\u003cp\u003e北京的雪已经连着下了两天了。\u003c/p\u003e\n\u003cp\u003e12月11日,也是因为下雪,晚上打车打到10点半才叫到车。\u003c/p\u003e\n\u003cp\u003e所以这次下雪后,晚上就早走了。\u003c/p\u003e\n\u003cp\u003e7:15坐上公司摆渡车,7:40左右到西二旗。\u003c/p\u003e\n\u003cp\u003e下车前刷微博看到有说昌平线故障,还没细看就到站下车了,还没想到事情的严重性。\u003c/p\u003e\n\u003cp\u003e路口已经有交警在指挥交通,看见两辆消防车在等红灯,事后想可能是去救援的。\u003c/p\u003e\n\u003cp\u003e走到进站口,就听到有人喊,“昌平线故障不停车,请更换其他交通工具”。\u003c/p\u003e\n\u003cp\u003e我坐13号线,继续往前走,就看到了下面的情景。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG150_hu1184931133622624991.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG150_hu1013573127101826915.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG150.jpg\" width=\"1707\" height=\"1280\" alt=\"西二旗进站口\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e栏杆里全是人,后面的人还在不断的往里进。\u003c/p\u003e\n\u003cp\u003e看了下门口的人不动,人太多,不敢进去,先看看情况。\u003c/p\u003e\n\u003cp\u003e过了一会,有人喊13号线也停了,扭头赶紧走。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG151_hu5204297538520097810.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG151_hu4695072437237824166.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG151.jpg\" width=\"1080\" height=\"2340\" alt=\"打车等候人数\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e第一反应打车,看了下,排队900人,直接放弃,换公交。\u003c/p\u003e\n\u003cp\u003e最近的公交站在西二旗大街,走吧。\u003c/p\u003e\n\u003cp\u003e到公交站一看,人更多,本来路就窄,全是人,走都没法走。\u003c/p\u003e\n\u003cp\u003e公交也没戏了,继续走吧,走过这段到前面看看再。\u003c/p\u003e\n\u003cp\u003e事后看说有的车被挤爆了,吓的别的车都不敢开门了。\u003c/p\u003e\n\u003cp\u003e走的有点冷,闻见一股香味,一看手机快8点半了,找个地吃点东西先。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG152_hu11895712938102014864.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG152_hu18213560282436426467.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG152.jpg\" width=\"1707\" height=\"1280\" alt=\"吉祥馄饨\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e一碗馄饨下肚,暖和多了,继续上路。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG153_hu998159380452732360.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/WechatIMG153_hu4813959692554313898.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG153.jpg\" width=\"1280\" height=\"1707\" alt=\"路上\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e走的小腿有点酸了,终于到公交站了。\u003c/p\u003e\n\u003cp\u003e等了一小会,总算上车了。\u003c/p\u003e\n\u003cp\u003e车也不敢开的快,10迈左右。\u003c/p\u003e\n\u003cp\u003e晚上10点,终于到家了。\u003c/p\u003e\n\u003cp\u003e路上花费\u003cstrong\u003e2小时45分钟\u003c/strong\u003e。\u003c/p\u003e\n\u003ch4 id=\"12月15日-周五\"\u003e12月15日 周五\u003c/h4\u003e\n\u003cp\u003e早上坐公交到地铁站。\u003c/p\u003e\n\u003cfigure\u003e\n \u003cvideo src=QWh2AALxlx08aXnxzCB201041200h2nD0E018.mp4 controls\u003e\n 你的浏览器不支持 \u003ccode\u003evideo\u003c/code\u003e 标签。\n \u003c/video\u003e\n\u003c/figure\u003e\n\u003cp\u003e霍营地铁站盛况,人挤满了整个通道。\u003c/p\u003e\n\u003cp\u003e立马出站,换乘公交。\u003c/p\u003e\n\u003cp\u003e路上花费\u003cstrong\u003e2小时\u003c/strong\u003e。\u003c/p\u003e\n","date_published":"2023-12-16T10:15:33+08:00","tags":[]},{"title":"使用Hugo实现响应式和优化的图片","id":"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/","url":"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/","summary":"\u003cp\u003e继续我们的\u003ca href=\"/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/\"\u003e博客优化之旅\u003c/a\u003e,本篇内容我们将介绍如何使用\u003ccode\u003eHugo\u003c/code\u003e实现响应式和优化的图片。\u003c/p\u003e\n\u003ch4 id=\"问题\"\u003e问题\u003c/h4\u003e\n\u003cp\u003e在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章\u003ca href=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/\"\u003e累计布局偏移修复方案改进 —— 自动生成图片宽高\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e经过一段运行后,发现这里有一个弊端。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRun hugo --gc --minify --cleanDestinationDir\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStart building sites … \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eERROR Failed to get JSON resource \u0026#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo\u0026amp;t=1698674110\u0026#34;: Get \u0026#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo\u0026amp;t=1698674110\u0026#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eERROR Failed to get JSON resource \u0026#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo\u0026amp;t=1698674110\u0026#34;: Get \u0026#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo\u0026amp;t=1698674110\u0026#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。\u003c/p\u003e","content_html":"\u003cp\u003e继续我们的\u003ca href=\"/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/\"\u003e博客优化之旅\u003c/a\u003e,本篇内容我们将介绍如何使用\u003ccode\u003eHugo\u003c/code\u003e实现响应式和优化的图片。\u003c/p\u003e\n\u003ch4 id=\"问题\"\u003e问题\u003c/h4\u003e\n\u003cp\u003e在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章\u003ca href=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/\"\u003e累计布局偏移修复方案改进 —— 自动生成图片宽高\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e经过一段运行后,发现这里有一个弊端。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRun hugo --gc --minify --cleanDestinationDir\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStart building sites … \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eERROR Failed to get JSON resource \u0026#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo\u0026amp;t=1698674110\u0026#34;: Get \u0026#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo\u0026amp;t=1698674110\u0026#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eERROR Failed to get JSON resource \u0026#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo\u0026amp;t=1698674110\u0026#34;: Get \u0026#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo\u0026amp;t=1698674110\u0026#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。\u003c/p\u003e\n\u003cp\u003e失败的时候,需要手动重跑构建,自动化发布卡壳了。\u003c/p\u003e\n\u003ch4 id=\"优化\"\u003e优化\u003c/h4\u003e\n\u003cp\u003e经过一番搜索,发现其实\u003ccode\u003eHugo\u003c/code\u003e本身是支持图片处理能力的。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eImage processing\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eResize, crop, rotate, filter, and convert images.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://gohugo.io/content-management/image-processing/\"\u003ehttps://gohugo.io/content-management/image-processing/\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e下面以我使用的\u003ca href=\"https://github.com/adityatelange/hugo-PaperMod/\"\u003ePaperMod主题\u003c/a\u003e为例,讲下如何通过\u003ccode\u003eimage processing\u003c/code\u003e实现图片响应式优化。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eimage processing\u003c/code\u003e需要用到\u003ccode\u003ePage bundles\u003c/code\u003e,所以\u003cstrong\u003e文章目录结构需要调整\u003c/strong\u003e,\n将一篇文章的资源(md文件,图片等)放在一个目录下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtent/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e├── posts\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ ├── my-post\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ │ ├── content1.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ │ ├── content2.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ │ ├── image1.jpg\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ │ ├── image2.png\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ │ └── index.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ └── my-other-post\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e│ └── index.md\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e目录结构调整完毕后,接下来修改图片显示文件代码。\u003c/p\u003e\n\u003cp\u003e这里需要生成\u003ccode\u003ewebp\u003c/code\u003e格式图片,所以\u003cstrong\u003e需要使用\u003ccode\u003ehugo\u003c/code\u003e的extended版本\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ePagerMod\u003c/code\u003e主题涉及到图片显示的一共三个文件:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e_default/_markup/render-image.html,对应markdown图片语法解析。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e slice \u003cspan style=\"color:#ae81ff\"\u003e480\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e720\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1080\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}} \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e生成的图片规格\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;(min-width: 768px) 720px, 100vw\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eholder :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GIP\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;photo\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;box\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eDestination :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDestination \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eText :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eText \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eTitle :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTitle \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e内容图片响应式开关配置,默认为\u003c/span\u003etrue\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default true }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResources\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGetMatch \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDestination }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e只有使用了\u003c/span\u003ehugo扩展版本的\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,才生成\u003c/span\u003ewebp格式图片\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e hugo\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsExtended (ne \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMediaType\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x webp \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL }} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $src.MediaType.Type }}\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ge \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x jpg \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL}} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}, {{\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePermalink }} {{printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ew\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth)}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Destination | safeURL }}\u0026#34;\u003c/span\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Height }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Text }}\u0026#34;\u003c/span\u003e title\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Title }}\u0026#34;\u003c/span\u003e loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Destination | safeURL }}\u0026#34;\u003c/span\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $src.Width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $src.Height }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Text }}\u0026#34;\u003c/span\u003e title\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Title }}\u0026#34;\u003c/span\u003e loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003epartials/cover.html,对应文章封面解析。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e slice \u003cspan style=\"color:#ae81ff\"\u003e480\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e720\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1080\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;(min-width: 768px) 720px, 100vw\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eholder :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GIP\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;photo\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;box\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecxt}} {{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Apply proper context from dict \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage (\u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eisHidden)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealt \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecaption \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e plainify) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003efigure \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;entry-cover\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e封面响应式图片配置开关,默认为\u003c/span\u003etrue\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default true }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elinkFullImages (\u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eIsHome)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResources\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eByType \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGetMatch (printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e*\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e i\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ee it is present \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e page bundle \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;noopener noreferrer\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e hugo\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsExtended (ne \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMediaType\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x webp \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL }} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.MediaType.Type }}\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ge \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x jpg \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL}} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}, {{\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePermalink }} {{printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ew\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth)}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Permalink }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Height }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Unprocessable image \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e responsive images disabled \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e For absolute urls \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e external links, no img processing here \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;noopener noreferrer\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Display Caption \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eIsHome }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecaption }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e markdownify }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003efigure\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e End image \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e End context \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eshortcodes/figure.html,对应文章内\u003ccode\u003efigure\u003c/code\u003e语法解析。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e slice \u003cspan style=\"color:#ae81ff\"\u003e480\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e720\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1080\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;(min-width: 768px) 720px, 100vw\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eholder :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GIP\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;photo\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;box\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;src\u0026#34;\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealign :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;align\u0026#34;\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;alt\u0026#34;\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecaption :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caption\u0026#34;\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e内容图片响应式开关配置,默认为\u003c/span\u003etrue\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default true }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003efigure{{ \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;class\u0026#34;\u003c/span\u003e) (eq (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;align\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;center\u0026#34;\u003c/span\u003e) }} \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e eq (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;align\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;center\u0026#34;\u003c/span\u003e }}align\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecenter {{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;class\u0026#34;\u003c/span\u003e }}{{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;link\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Get \u0026#34;\u003c/span\u003elink\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}\u0026#34;\u003c/span\u003e{{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;target\u0026#34;\u003c/span\u003e }} target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end }}{{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rel\u0026#34;\u003c/span\u003e }} rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end }}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003ePage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResources\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGetMatch (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;src\u0026#34;\u003c/span\u003e) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e hugo\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsExtended (ne \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMediaType\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eType \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x webp \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehint \u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL }} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $src.MediaType.Type }}\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erespSizes \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e range \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e ge \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ei }}, {{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (print \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x jpg \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilter) )\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eRelPermalink \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e absURL}} {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}w\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}, {{\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePermalink }} {{printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ew\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth)}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; sizes=\u0026#34;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edataSzes }}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; /\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $src }}{{- if eq ($align) \u0026#34;\u003c/span\u003ecenter\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}#center{{- end }}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealt) (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecaption) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}} width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esrc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eHeight \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}} height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;!--\u003c/span\u003e Closing img tag \u003cspan style=\"color:#f92672\"\u003e--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;link\u0026#34;\u003c/span\u003e }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;title\u0026#34;\u003c/span\u003e) (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caption\u0026#34;\u003c/span\u003e)) (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;attr\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003efigcaption\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ with (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;title\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caption\u0026#34;\u003c/span\u003e) (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;attr\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caption\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e markdownify \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;attrlink\u0026#34;\u003c/span\u003e }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;attr\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e markdownify \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;attrlink\u0026#34;\u003c/span\u003e }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003efigcaption\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003efigure\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"使用效果\"\u003e使用效果\u003c/h4\u003e\n\u003cp\u003e正常插入\u003ccode\u003ejpg/png\u003c/code\u003e图片,构建后会自动生成webp/原始格式下不同规格的图片。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003emarkdown图片显示\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/render-image_hu10785117163759029787.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/render-image_hu6098837098373363195.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"render-image.png\" width=\"1730\" height=\"1294\" alt=\"render-image\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003efigure短代码显示\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/figure_hu13810583128976659866.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/figure_hu15918180951896956988.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"figure.png\" width=\"1720\" height=\"1220\" alt=\"figure\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e封面显示\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/cover_hu16575402247833177964.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/responsive-and-optimized-images-with-hugo/cover_hu4064065245092380092.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"cover.png\" width=\"1732\" height=\"1076\" alt=\"cover\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e提示:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStart building sites \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e…\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo v0.\u003cspan style=\"color:#ae81ff\"\u003e96\u003c/span\u003e.\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003eextended darwin\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003earm64 BuildDate\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eunknown\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eError: Error building site: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1\u0026#34;\u003c/span\u003e: timed \u003cspan style=\"color:#66d9ef\"\u003eout\u003c/span\u003e initializing value. You may have a circular \u003cspan style=\"color:#66d9ef\"\u003eloop\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e a shortcode, \u003cspan style=\"color:#66d9ef\"\u003eor\u003c/span\u003e your site may have resources that take longer \u003cspan style=\"color:#66d9ef\"\u003eto\u003c/span\u003e build than the \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003etimeout\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elimit\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e your Hugo config file.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBuilt \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e22356\u003c/span\u003e ms\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以通过修改配置文件\u003ccode\u003econfig.yml\u003c/code\u003e,新增\u003ccode\u003etimeout\u003c/code\u003e配置,调大超时时间解决。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuildDrafts: false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuildFuture: false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuildExpired: false\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etimeout: 60s // 调大此处的时间即可\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e终于知道为啥\u003ccode\u003ePagerMod\u003c/code\u003e主题默认只有封面下才有生成不同规格的逻辑了。\u003c/p\u003e\n","date_published":"2023-12-10T08:29:05+08:00","tags":["hugo","博客优化"]},{"title":"加速Google Analytics","id":"https://liudon.com/posts/optimize-google-analytics/","url":"https://liudon.com/posts/optimize-google-analytics/","summary":"\u003ch3 id=\"起因\"\u003e起因\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eGoogle Analytics\u003c/code\u003e是一款优秀的流量分析服务,集成方便,使用简单。\u003c/p\u003e\n\u003cp\u003e最近在优化页面访问速度,发现\u003ccode\u003eGoogle Analytics\u003c/code\u003e是一个优化点。\u003c/p\u003e\n\u003ch3 id=\"优化\"\u003e优化\u003c/h3\u003e\n\u003ch4 id=\"1-访问加速\"\u003e1. 访问加速\u003c/h4\u003e\n\u003cp\u003e国内访问\u003ccode\u003eGoogle Analytics\u003c/code\u003e很慢,同时还面临着各种广告屏蔽插件拦截。\u003c/p\u003e","content_html":"\u003ch3 id=\"起因\"\u003e起因\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eGoogle Analytics\u003c/code\u003e是一款优秀的流量分析服务,集成方便,使用简单。\u003c/p\u003e\n\u003cp\u003e最近在优化页面访问速度,发现\u003ccode\u003eGoogle Analytics\u003c/code\u003e是一个优化点。\u003c/p\u003e\n\u003ch3 id=\"优化\"\u003e优化\u003c/h3\u003e\n\u003ch4 id=\"1-访问加速\"\u003e1. 访问加速\u003c/h4\u003e\n\u003cp\u003e国内访问\u003ccode\u003eGoogle Analytics\u003c/code\u003e很慢,同时还面临着各种广告屏蔽插件拦截。\u003c/p\u003e\n\u003cp\u003e这里借助\u003ccode\u003eCloudflare Worker\u003c/code\u003e实现\u003ccode\u003eGoogle Analytics\u003c/code\u003e反代,同时更换采集路由规避广告屏蔽插件的拦截。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eCloudflare\u003c/code\u003e新建Worker,代码如下,保存后部署。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaddEventListener(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;fetch\u0026#39;\u003c/span\u003e, (event) \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e这里可以加\u003c/span\u003e filter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e event\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erespondWith(handleRequest(event));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e worker \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e应用的路由地址,末尾不加\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,改为你的博客地址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e DOMAIN \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;xxx.com\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e博客插入的\u003c/span\u003ejs地址文件名\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,可自定义\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e JS_FILE \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;ga.js\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e响应上报的接口路径,可自定义,规避广告屏蔽插件拦截\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e COLLECT_PATH \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;collect_path\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e原\u003c/span\u003e gtag \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e地址,将\u003c/span\u003eG\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eXXX改为你的id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e JS_URL \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://www.googletagmanager.com/gtag/js?id=G-XXX\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e下面不需要改\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e G_DOMAIN \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;google-analytics.com\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e G_COLLECT_PATH \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;g\\/collect\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e async function handleRequest(event) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e event\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eurl;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (url\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ematch(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{JS_FILE}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e requestJs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e await (await fetch(JS_URL))\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e jsText \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e requestJs\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplaceAll(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ewww\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplaceAll(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e G_DOMAIN, DOMAIN)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplaceAll(G_COLLECT_PATH, COLLECT_PATH);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Response(jsText, {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e status: \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e statusText: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;OK\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e headers: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;application/javascript\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (url\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ematch(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{COLLECT_PATH}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e newReq \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e await readRequest(event\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erequest);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e event\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewaitUntil(fetch(newReq));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Response(null, {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e status: \u003cspan style=\"color:#ae81ff\"\u003e204\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e statusText: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;No Content\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e async function readRequest(request) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e { url, headers } \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e request;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e body \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e await request\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e ga_url \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e url\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplace(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{COLLECT_PATH}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003ewww\u003cspan style=\"color:#f92672\"\u003e.$\u003c/span\u003e{G_DOMAIN}\u003cspan style=\"color:#f92672\"\u003e/$\u003c/span\u003e{G_COLLECT_PATH}\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e nq \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e method: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;POST\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e headers: {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Host: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;www.google-analytics.com\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Origin: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;origin\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Cache-Control\u0026#39;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;max-age=0\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;User-Agent\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;user-agent\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Accept: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;accept\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Accept-Language\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;accept-language\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content-type\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;text/plain\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Referer: headers\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;referer\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e body: body,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e new Request(ga_url, nq);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e页面插入对应js\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;!-- Google tag (gtag.js) --\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;script async src=\u0026#34;https://xxx.com/ga.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;script\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e window.dataLayer = window.dataLayer || [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e function gtag(){dataLayer.push(arguments);}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gtag(\u0026#39;js\u0026#39;, new Date());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e gtag(\u0026#39;config\u0026#39;, \u0026#39;G-XXX\u0026#39;); // 将G-XXX改为你的id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/script\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"2-文件瘦身\"\u003e2. 文件瘦身\u003c/h4\u003e\n\u003cp\u003e通过\u003ca href=\"https://gtmetrix.com/\"\u003e性能分析\u003c/a\u003e,发现js文件过大,影响页面加载速度。\u003c/p\u003e\n\u003cp\u003e虽然使用了\u003ccode\u003eCloudfare\u003c/code\u003e代理,但是\u003ccode\u003eGoogle Analytics\u003c/code\u003e原始的js文件为80KB左右。\u003c/p\u003e\n\u003cp\u003e搜索一番,找到一个\u003ca href=\"https://minimalanalytics.com/\"\u003e瘦身版Google Analytics\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMinimal Google Analytics Snippet\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eA simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBefore\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGoogle Tag Manager + Analytics = 73kB\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSnippet = 1.5kB\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e插入的js片断,只有1.5kb大小。\u003c/p\u003e\n\u003cp\u003e唯一的缺点就是只有基本功能,这对于我们来说足够了。\u003c/p\u003e\n\u003cp\u003e将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003escript\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eenScroll\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,enFdl\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,extCurrent\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,filename\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,targetText\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,splitOrigin\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e lStor\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003elocalStorage,sStor\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003esessionStorage,doc\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocument,docEl\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocument\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edocumentElement,docBody\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocument\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ebody,docLoc\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocument\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elocation,w\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ewindow,s\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003escreen,nav\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003enavigator\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e{},extensions\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pdf\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xls\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;xlsx\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;doc\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;docx\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;txt\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rtf\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;csv\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;exe\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pps\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ppt\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pptx\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;7z\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;pkg\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rar\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gz\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;zip\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;avi\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mov\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mp4\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mpe\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mpeg\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;wmv\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mid\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;midi\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;mp3\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;wav\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;wma\u0026#34;\u003c/span\u003e];function a(e,t,n,o){\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e j\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;G-G9ZDJQN9E2\u0026#34;\u003c/span\u003e,r\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003eMath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efloor(Math\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erandom()\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1e9\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,c\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003eMath\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003efloor(Date\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003enow()\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1e3\u003c/span\u003e),F\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_p\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_p\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003er()),sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_p),E\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003er()\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ec(),_\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e(lStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecid_v4\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e(lStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecid_v4\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eE()),lStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecid_v4),m\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003elStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetItem(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cid_v4\u0026#34;\u003c/span\u003e),v\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003em\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e:enScroll\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e,p\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esid\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esid\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ec()),sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esid),O\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003esStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_ss)\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_ss\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e,sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e_ss;\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetItem(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_ss\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e void \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e},a\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e,g\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esct)\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(enScroll\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esct;\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e x\u003cspan style=\"color:#f92672\"\u003e=+\u003c/span\u003esStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetItem(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sct\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ea,sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esct\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ex;\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esct\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ea;\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e sStor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esct},i\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocLoc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esearch,b\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003enew URLSearchParams(i),h\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;q\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;s\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;search\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;query\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;keyword\u0026#34;\u003c/span\u003e],y\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eh\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esome(e\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003ei\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eincludes(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026amp;\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003ei\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eincludes(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e)),u\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003ey\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;view_search_results\u0026#34;\u003c/span\u003e:enScroll\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;scroll\u0026#34;\u003c/span\u003e:enFdl\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;file_download\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;page_view\u0026#34;\u003c/span\u003e,f\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003eenScroll\u003cspan style=\"color:#f92672\"\u003e==!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;90\u0026#34;\u003c/span\u003e:void \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,C\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e{\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(u()\u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;view_search_results\u0026#34;\u003c/span\u003e){\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e(let e of b)\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(h\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eincludes(e[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]))\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e e[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e]}\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e void \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e},d\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eencodeURIComponent,k\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e{let t\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e[];\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e(let n \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e e)e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehasOwnProperty(n)\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003ee[n]\u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003et\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epush(d(n)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ed(e[n]));\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e t\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejoin(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026amp;\u0026#34;\u003c/span\u003e)},A\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,S\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://xxx.com/collect_path\u0026#34;\u003c/span\u003e,M\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ek({v:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2\u0026#34;\u003c/span\u003e,tid:j,_p:F(),sr:(s\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewidth\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003ew\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edevicePixelRatio\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;x\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003es\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eheight\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003ew\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003edevicePixelRatio)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoString(),ul:(nav\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elanguage\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etoLowerCase(),cid:_(),_fv:v(),_s:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e,dl:docLoc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eorigin\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003edocLoc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epathname\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ei,dt:doc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etitle\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,dr:doc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereferrer\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,sid:p(),sct:g(),seg:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e,en:u(),\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;epn.percent_scrolled\u0026#34;\u003c/span\u003e:f(),\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ep.search_term\u0026#34;\u003c/span\u003e:C(),\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ep.file_extension\u0026#34;\u003c/span\u003e:e\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ep.file_name\u0026#34;\u003c/span\u003e:t\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ep.link_text\u0026#34;\u003c/span\u003e:n\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;ep.link_url\u0026#34;\u003c/span\u003e:o\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003evoid \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,_ss:O(),_dbg:A\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e:void \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e}),l\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eS\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003eM;\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(nav\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esendBeacon)nav\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esendBeacon(l);\u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e{let e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003enew XMLHttpRequest;e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eopen(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;POST\u0026#34;\u003c/span\u003e,l,\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)}}a();function sPr(){\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e(docEl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escrollTop\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003edocBody\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escrollTop)\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e((docEl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escrollHeight\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003edocBody\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003escrollHeight)\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edocEl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eclientHeight)\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e}doc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaddEventListener(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;scroll\u0026#34;\u003c/span\u003e,sEv,{passive:\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e});function sEv(){\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003esPr();\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e90\u003c/span\u003e)\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;enScroll\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,a(),doc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eremoveEventListener(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;scroll\u0026#34;\u003c/span\u003e,sEv,{passive:\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e}),enScroll\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e}document\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaddEventListener(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DOMContentLoaded\u0026#34;\u003c/span\u003e,function(){let e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocument\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetElementsByTagName(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a\u0026#34;\u003c/span\u003e);\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e(let t\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;t\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elength;t\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e)\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e(e[t]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;href\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003enull){\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e n\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ee[t]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;href\u0026#34;\u003c/span\u003e),s\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003en\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esubstring(n\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elastIndexOf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e),o\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003es\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epop();(e[t]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ehasAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;download\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003eextensions\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eincludes(o))\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003ee[t]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eaddEventListener(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;click\u0026#34;\u003c/span\u003e,fDl,{passive:\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e})}});function fDl(e){enFdl\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e t\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecurrentTarget\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;href\u0026#34;\u003c/span\u003e),n\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003et\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esubstring(t\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elastIndexOf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e),s\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003en\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003epop(),o\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003en\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplace(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003es,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e),i\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ee\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecurrentTarget\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etext,r\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003et\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ereplace(docLoc\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eorigin,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e);a(s,o,i,r),enFdl\u003cspan style=\"color:#f92672\"\u003e=!\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003escript\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"效果\"\u003e效果\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/optimize-google-analytics/gtmetrix-report_hu7862777591110582981.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/optimize-google-analytics/gtmetrix-report_hu9813378589716916173.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"gtmetrix-report.png\" width=\"2112\" height=\"748\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2023-12-02T09:25:49+08:00","tags":["google analytics","cloudflare","博客优化"]},{"title":"使用Google Indexing API加速博客收录","id":"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/","url":"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/","summary":"\u003cp\u003e对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。\u003c/p\u003e\n\u003cp\u003e今天,我们就来介绍一种利用\u003ccode\u003eGoogle Indexing API\u003c/code\u003e接口,通过\u003ccode\u003eGithub Actions\u003c/code\u003e实现部署时通知\u003ccode\u003eGoogle\u003c/code\u003e抓取页面内容。\u003c/p\u003e","content_html":"\u003cp\u003e对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。\u003c/p\u003e\n\u003cp\u003e今天,我们就来介绍一种利用\u003ccode\u003eGoogle Indexing API\u003c/code\u003e接口,通过\u003ccode\u003eGithub Actions\u003c/code\u003e实现部署时通知\u003ccode\u003eGoogle\u003c/code\u003e抓取页面内容。\u003c/p\u003e\n\u003cp\u003e操作步骤:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e申请\u003ccode\u003eGoogle API\u003c/code\u003e凭证\u003c/p\u003e\n\u003cp\u003e访问\u003ca href=\"https://console.cloud.google.com/\"\u003eGoogle Cloud控制台\u003c/a\u003e,如果没有项目,点击选择项目,然后新建项目。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu3640116897298145221.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu13499488781587240421.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231027193906.png\" width=\"1752\" height=\"759\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e选择对应项目,点击\u003ccode\u003eIAM和管理\u003c/code\u003e标签,点击\u003ccode\u003e服务帐号\u003c/code\u003e,选择新建服务帐号。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu14161076425847383800.webp 670w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu10507043871738030836.png 670w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231027194257.png\" width=\"670\" height=\"581\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e服务帐号名称:自己起个名字即可\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e服务帐号id:不需要修改,自动生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e服务角色:Owner\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e填写相关信息后,点击完成创建好服务帐号。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu112325605454886179.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu1820397607132649156.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231027194753.png\" width=\"1662\" height=\"424\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。\u003c/p\u003e\n\u003cp\u003e点击后面的三个点按钮,选择管理密钥。\u003c/p\u003e\n\u003cp\u003e点击添加密钥-\u0026gt;新建密钥,选择\u003ccode\u003eJSON\u003c/code\u003e格式,点击创建会下载一个文件,这里后面会用到。\u003c/p\u003e\n\u003cp\u003e回到首页,点击\u003ccode\u003eAPI和服务\u003c/code\u003e,点击\u003ccode\u003e启用API和服务\u003c/code\u003e,搜索框输入\u003ccode\u003eIndexing\u003c/code\u003e,选择\u003ccode\u003eWeb Search Indexing API\u003c/code\u003e,点击启用即可。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e将服务账号关联到\u003ccode\u003eGoogle Search Console\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e进入\u003ca href=\"https://search.google.com/\"\u003eGoogle Search Console控制台\u003c/a\u003e,选择你的网站。\u003c/p\u003e\n\u003cp\u003e找到设置里的用户和权限,点击添加用户。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu16459305612703530959.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu15241845893383975374.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202310280923031.png\" width=\"2034\" height=\"926\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e邮箱地址:填写第一步分配的邮箱地址\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e权限:选择拥有者\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e配置\u003ccode\u003eGithub Action\u003c/code\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e添加\u003ccode\u003eSecret\u003c/code\u003e变量,变量key为\u003ccode\u003eGOOGLE_INDEXING_API_TOKEN\u003c/code\u003e,值为前面下载文件的内容。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e编辑\u003ccode\u003eworkflow\u003c/code\u003e编排任务,新增步骤\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: easyindex\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;${{ secrets.GOOGLE_INDEXING_API_TOKEN }}\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003ecredentials\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ejson\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e touch \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eurl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecsv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003enotification_type\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eurl\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eurl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecsv \u003cspan style=\"color:#75715e\"\u003e# Headers line\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eURL_UPDATED\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ehttps://liudon.com/\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eurl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecsv \u003cspan style=\"color:#75715e\"\u003e# ADD URL 这里改为你的博客首页\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eURL_UPDATED\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ehttps://liudon.com/sitemap.xml\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eurl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecsv \u003cspan style=\"color:#75715e\"\u003e# ADD URL 这里改为你的sitemap地址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e curl \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003es \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eL https:\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003egithub\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecom\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eusk81\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eeasyindex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecli\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ereleases\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003edownload\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ev1\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0.6\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eeasyindex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecli_1\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0.6\u003c/span\u003e_linux_amd64\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003etar\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egz \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e tar xz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e chmod \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ex \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eeasyindex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecli\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eeasyindex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecli google \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ec \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eurl\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecsv\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu1630441103893198177.webp 444w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu14301052623136109094.png 444w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231027200238.png\" width=\"444\" height=\"212\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n","date_published":"2023-10-27T19:32:24+08:00","tags":["google","github","hugo"]},{"title":"在Netlify上部署Twikoo评论系统","id":"https://liudon.com/posts/deploy-twikoo-on-netlify/","url":"https://liudon.com/posts/deploy-twikoo-on-netlify/","summary":"\u003cblockquote\u003e\n\u003cp\u003e在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cem\u003e2024年7月30日更新:因为\u003ca href=\"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting\"\u003eGithub接口策略调整\u003c/a\u003e,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。\u003c/em\u003e\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003e在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cem\u003e2024年7月30日更新:因为\u003ca href=\"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting\"\u003eGithub接口策略调整\u003c/a\u003e,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。\u003c/em\u003e\u003c/p\u003e\n\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e博客之前通过\u003ccode\u003eVercel\u003c/code\u003e部署了\u003ccode\u003eTwikoo\u003c/code\u003e评论系统,但是最近发现加载很慢。\u003c/p\u003e\n\u003cp\u003e看\u003ca href=\"https://twikoo.js.org/backend.html\"\u003eTwikoo官网文档\u003c/a\u003e,\u003ccode\u003eNetlify\u003c/code\u003e比\u003ccode\u003eVercel\u003c/code\u003e国内访问会更优一些,于是搞了一把迁移。\u003c/p\u003e\n\u003cp\u003e迁移过程中遇到了一些问题,网上搜了一番,发现\u003ccode\u003eNetlify\u003c/code\u003e下部署\u003ccode\u003eTwikoo\u003c/code\u003e的信息很少,这篇文章我会介绍整个操作过程。\u003c/p\u003e\n\u003ch4 id=\"部署\"\u003e部署\u003c/h4\u003e\n\u003cp\u003e参考\u003ca href=\"https://twikoo.js.org/backend.html#netlify-%E9%83%A8%E7%BD%B2\"\u003e官网文档\u003c/a\u003e,部署即可。\u003c/p\u003e\n\u003cp\u003e这里一开始Fork成了\u003ccode\u003ehttps://github.com/twikoojs/twikoo\u003c/code\u003e,导致部署后访问404。\u003c/p\u003e\n\u003cp\u003e注意,正确的仓库是\u003ccode\u003ehttps://github.com/twikoojs/twikoo-netlify\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu8628832215964479861.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu16486534993597105028.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"netlify-3.4f565e5d.png\" width=\"1920\" height=\"925\" alt=\"Netlify部署Twikoo\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e使用同一个\u003ccode\u003eMongoDB\u003c/code\u003e,配置原来的地址,这样已有的评论也不会丢。\u003c/p\u003e\n\u003cp\u003e部署后,通过\u003ccode\u003ehttps://comment.liudon.com\u003c/code\u003e访问,返回200。\u003c/p\u003e\n\u003cp\u003e但是访问\u003ccode\u003ehttps://liudon.com\u003c/code\u003e,提示\u003ccode\u003eCORS跨域错误\u003c/code\u003e,搜索一番网上资料也没找到相关信息。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019190935_hu11946659690224258165.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019190935_hu3943517669446501926.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231019190935.png\" width=\"1484\" height=\"425\" alt=\"CORS报错\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用\u003ccode\u003eNetlify\u003c/code\u003e来新增返回头规避。\u003c/p\u003e\n\u003cp\u003e仓库下新增\u003ccode\u003enetlify.toml\u003c/code\u003e文件,针对根目录返回跨域头,内容如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[[headers]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Define which paths this specific [[headers]] block will cover.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e for = \u0026#34;/\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [headers.values]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Access-Control-Allow-Origin = \u0026#34;*\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Access-Control-Allow-Headers = \u0026#34;Content-Type\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Access-Control-Allow-Methods = \u0026#34;*\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e再次刷新页面,报错又变成了404,但是直接访问\u003ccode\u003ehttps://comment.liudon.com\u003c/code\u003e地址是返回的200。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191326_hu8838313001624003405.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191326_hu2551903595337723990.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231019191326.png\" width=\"1502\" height=\"366\" alt=\"404报错\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e经过一番定位,发现了问题所在:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etwikoo-netlify\u003c/code\u003e库根目录地址是通过\u003ccode\u003eLocation\u003c/code\u003e跳转到的\u003ccode\u003e/.netlify/functions/twikoo\u003c/code\u003e接口,\u003ccode\u003e/.netlify/functions/twikoo\u003c/code\u003e才是真正处理的接口。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191522_hu5359049959332663132.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191522_hu11483385048471464107.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231019191522.png\" width=\"1605\" height=\"422\" alt=\"twikoo-netlify根目录返回\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e而\u003ccode\u003evercel-netlify\u003c/code\u003e库是通过rewrite将所有请求转发到了\u003ccode\u003e/api/index\u003c/code\u003e接口。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191833_hu7525574806016549572.webp 1012w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019191833_hu15007640604031802936.png 1012w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231019191833.png\" width=\"1012\" height=\"442\" alt=\"twikoo-vercel转发\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e博客里写的\u003ccode\u003eTwikoo\u003c/code\u003e环境id填的是\u003ccode\u003ehttps://comment.liudon.com\u003c/code\u003e,更换为\u003ccode\u003eNetlify\u003c/code\u003e后,需要更换环境id,补齐后面的\u003ccode\u003e/.netlify/functions/twikoo\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019192158_hu4392240285882896864.webp 663w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-twikoo-on-netlify/20231019192158_hu696688395608897294.png 663w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231019192158.png\" width=\"663\" height=\"137\" alt=\"twikoo环境id\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e因为\u003ccode\u003eNetlify\u003c/code\u003e也支持\u003ccode\u003eRewrite\u003c/code\u003e转发,所以决定还是通过\u003ccode\u003enetlify.toml\u003c/code\u003e文件增加转发配置解决,内容如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[[redirects]]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e from = \u0026#34;/\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e to = \u0026#34;/.netlify/functions/twikoo\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e status = 200\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e force = true\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e注意一定不要漏了\u003ccode\u003eforce\u003c/code\u003e参数,因为根目录文件是存在的,不带这个参数的话\u003ccode\u003eRewrite\u003c/code\u003e是不生效的,必须指定这个。\u003c/p\u003e\n\u003cp\u003e这下访问彻底ok了。\u003c/p\u003e\n\u003cp\u003e不过很快又发现另外一个问题,看到别人博客上\u003ccode\u003eTwikoo\u003c/code\u003e版本已经是\u003ccode\u003e1.6.22\u003c/code\u003e,自己的还是\u003ccode\u003e1.5.11\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e搜索一番后,\u003ccode\u003eTwikoo\u003c/code\u003e已经给出了更新操作:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e针对 Netlify 部署的更新方式\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2. 打开 package.json,点击编辑\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e3. 将 \u0026#34;twikoo-vercel\u0026#34;: \u0026#34;latest\u0026#34; 其中的 latest 修改为最新版本号。点击 Commit changes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e4. 部署会自动触发\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e不过这样操作,每次版本更新,都需要手动去改一下\u003ccode\u003epackage.json\u003c/code\u003e文件的版本,重新构建。\u003c/p\u003e\n\u003cp\u003e对于一个程序员,我们的追求就是自动化。\u003c/p\u003e\n\u003ch4 id=\"自动版本更新\"\u003e自动版本更新\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e服务端版本自动更新\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这里利用\u003ccode\u003eGithub Actions\u003c/code\u003e定时任务,通过接口拉取\u003ca href=\"https://github.com/twikoojs/twikoo/releases\"\u003etwikoo\u003c/a\u003e最新的版本,然后更新到\u003ccode\u003epackage.json\u003c/code\u003e文件,从而实现版本自动更新。\u003c/p\u003e\n\u003cp\u003e在自己\u003ccode\u003etwikoo-netlify\u003c/code\u003e仓库下,新增\u003ccode\u003eActions\u003c/code\u003e,代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# This is a basic workflow to help you get started with Actions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ename: CI\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# Controls when the workflow will run\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eon:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Triggers the workflow on push or pull request events but only for the \u0026#34;main\u0026#34; branch\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e push:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e branches: [ \u0026#34;main\u0026#34; ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e pull_request:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e branches: [ \u0026#34;main\u0026#34; ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Allows you to run this workflow manually from the Actions tab\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e workflow_dispatch:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e schedule:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - cron: \u0026#39;0 2 * * *\u0026#39; # 每天定时2点执行一次\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# A workflow run is made up of one or more jobs that can run sequentially or in parallel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ejobs:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # This workflow contains a single job called \u0026#34;build\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e build:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # The type of runner that the job will run on\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e runs-on: ubuntu-latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e permissions: \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e contents: write\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Steps represent a sequence of tasks that will be executed as part of the job\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e steps:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - uses: actions/checkout@v3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Runs a single command using the runners shell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: update version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e env:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e github_token: ${{ secrets.TOKEN }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e response=$(curl -sf -H \u0026#34;Authorization: token $github_token\u0026#34; -H \u0026#34;Accept: application/vnd.github+json\u0026#34; https://api.github.com/repos/twikoojs/twikoo/releases/latest)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e version=$(echo $response | jq -r \u0026#39;.tag_name\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if [ -n \u0026#34;$version\u0026#34; ]; then\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e sed -i \u0026#34;s/\\\u0026#34;twikoo-netlify\\\u0026#34;: \\\u0026#34;.*\\\u0026#34;/\\\u0026#34;twikoo-netlify\\\u0026#34;: \\\u0026#34;$version\\\u0026#34;/\u0026#34; package.json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e fi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e shell: bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e # Runs a set of commands using the runners shell\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Commit changes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: EndBug/add-and-commit@v9\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e env:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e github_token: ${{ secrets.TOKEN }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e add: .\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里会把修改后的\u003ccode\u003epackage.json\u003c/code\u003e文件提交到仓库,所以需要申请一个\u003ccode\u003eToken\u003c/code\u003e,可以参考我上一篇\u003ca href=\"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/\"\u003e文章\u003c/a\u003e申请,然后添加到仓库变量里。\u003c/p\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e博客引用版本自动更新\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。\u003c/p\u003e\n\u003cp\u003e但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。\u003c/p\u003e\n\u003cp\u003e因此还是决定在服务端部署时,获取最新版本号更新服务。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e引用版本配置化\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003ecomments.html\u003c/code\u003e文件修改:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;script src=\u0026#34;https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003econfig.tml\u003c/code\u003e配置修改:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eparams:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e env: production # to enable google analytics, opengraph, twitter-cards and schema.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ... # 其他配置\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e assets:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e disableHLJS: true # to disable highlight.js\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e twikoo:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e version: 1.5.11 # 配置twikoo版本号\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e获取最新版本号部署\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003eActions\u003c/code\u003e新增步骤:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Setup Hugo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: peaceiris/actions-hugo@v2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e with:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e hugo-version: \u0026#39;latest\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ####\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Get twikoo version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e id: twikoo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r \u0026#39;.dependencies.\u0026#34;twikoo-netlify\u0026#34;\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u0026#34;Twikoo version: $version\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u0026#34;twikoo_version=$version\u0026#34; \u0026gt;\u0026gt; $GITHUB_OUTPUT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Update config.yml version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: fjogeleit/yaml-update-action@main\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e with:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e valueFile: \u0026#39;config.yml\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e propertyPath: \u0026#39;params.twikoo.version\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e value: ${{ steps.twikoo.outputs.twikoo_version }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e commitChange: true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ####\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Build\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: hugo --gc --minify --cleanDestinationDir \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e####\u003c/code\u003e内代码即为获取版本号,更新\u003ccode\u003econfig.tml\u003c/code\u003e版本号逻辑,然后再进行\u003ccode\u003ehugo\u003c/code\u003e部署。\u003c/p\u003e\n\u003cp\u003e需要将\u003ccode\u003ehttps://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json\u003c/code\u003e这个url里的\u003ccode\u003eLiudon/twikoo-netlify\u003c/code\u003e改为你的仓库名。\u003c/p\u003e\n\u003cp\u003e这下后面\u003ccode\u003eTwikoo\u003c/code\u003e官方更新版本,博客的\u003ccode\u003eTwikoo\u003c/code\u003e也会跟着自动更新。\u003c/p\u003e\n","date_published":"2023-10-19T19:46:32+08:00","tags":["netlify","twikoo","github","vercel","hugo"]},{"title":"利用Github Actions定时抓取微博","id":"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/","url":"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/","summary":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e在微博上关注了一些用户,比如\u003ca href=\"https://weibo.com/u/1401527553\"\u003etk教主\u003c/a\u003e,\u003ca href=\"https://weibo.com/u/1670659923\"\u003e月风\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。\u003c/p\u003e\n\u003ch4 id=\"实现方案\"\u003e实现方案\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e整体思路:利用\u003ccode\u003eGithub Actions\u003c/code\u003e的\u003ccode\u003eScheduled\u003c/code\u003e任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。\u003c/strong\u003e\u003c/p\u003e","content_html":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e在微博上关注了一些用户,比如\u003ca href=\"https://weibo.com/u/1401527553\"\u003etk教主\u003c/a\u003e,\u003ca href=\"https://weibo.com/u/1670659923\"\u003e月风\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。\u003c/p\u003e\n\u003ch4 id=\"实现方案\"\u003e实现方案\u003c/h4\u003e\n\u003cp\u003e\u003cstrong\u003e整体思路:利用\u003ccode\u003eGithub Actions\u003c/code\u003e的\u003ccode\u003eScheduled\u003c/code\u003e任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。\u003c/strong\u003e\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e新建仓库,比如\u003ccode\u003eweibo_archive\u003c/code\u003e。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加抓取脚本,\u003ca href=\"https://github.com/Liudon/weibo_archive/blob/main/weibo_archive.sh\"\u003e完整代码\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e这里用到微博两个接口:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom\u0026amp;type=uid\u0026amp;value=$uid\u0026amp;containerid=107603$uid\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// 根据微博id,抓取微博完整的内容\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://m.weibo.cn/statuses/extend?id=$id\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加环境变量。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e进入\u003ccode\u003e个人设置\u003c/code\u003e-\u0026gt;\u003ccode\u003eDeveloper Settings\u003c/code\u003e-\u0026gt;\u003ccode\u003ePersonal access tokens\u003c/code\u003e-\u0026gt;\u003ccode\u003eTokens (classic)\u003c/code\u003e,创建新的Token,记下对应的值。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e进入第一步创建仓库的配置页,点击\u003ccode\u003eSecrets and variables\u003c/code\u003e下的\u003ccode\u003eActions\u003c/code\u003e:\u003c/p\u003e\n\u003cp\u003e切到\u003ccode\u003eSecret\u003c/code\u003e目录,创建新的\u003ccode\u003eSecret\u003c/code\u003e变量,名称为\u003ccode\u003eTOKEN\u003c/code\u003e,值为前一步记录的值;切到\u003ccode\u003eVariables\u003c/code\u003e目录,创建新的\u003ccode\u003eVariables\u003c/code\u003e变量,名称为\u003ccode\u003eWEIBO_UIDS\u003c/code\u003e,值为你需要抓取的微博用户id,多个用户的话以\u003ccode\u003e|\u003c/code\u003e分割。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加定时任务,完整yaml文件如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# This is a basic workflow to help you get started with Actions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ename: CI\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Controls when the workflow will run\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eon:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Triggers the workflow on push or pull request events but only for the \u0026#34;main\u0026#34; branch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epush:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e branches: [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;main\u0026#34;\u003c/span\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epull_request:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e branches: [ \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;main\u0026#34;\u003c/span\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Allows you to run this workflow manually from the Actions tab\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eworkflow_dispatch:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eschedule:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e cron: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*/5 * * * *\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# A workflow run is made up of one or more jobs that can run sequentially or in parallel\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ejobs:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# This workflow contains a single job called \u0026#34;build\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebuild:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# The type of runner that the job will run on\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e runs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eon: ubuntu\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elatest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e permissions: \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e contents: write\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Steps represent a sequence of tasks that will be executed as part of the job\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e steps:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e uses: actions\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003echeckout\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Runs a single command using the runners shell\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: archive weibo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e chmod \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003ex \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eweibo_archive\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003eweibo_archive\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e shell: bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e env:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e weibo_uids: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ vars\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eweibo_uids }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e# Runs a set of commands using the runners shell\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Commit changes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: EndBug\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eadd\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecommit\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ev9\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e env:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e github_token: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ secrets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTOKEN }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e add: \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"效果\"\u003e效果\u003c/h4\u003e\n\u003cp\u003e抓取后的内容,会按用户id分别保存到不同文件。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu10871856927439122702.webp 824w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu7733977588862373690.png 824w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20231007132025.png\" width=\"824\" height=\"207\" alt=\"效果\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e不过这个方案有一个唯一的缺点,\u003ccode\u003eGithub Actions\u003c/code\u003e定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNote: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_published":"2023-10-07T13:17:57+08:00","tags":["github","weibo"]},{"title":"北大口腔牙周刮治记录","id":"https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/","url":"https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/","summary":"\u003ch4 id=\"病情\"\u003e病情\u003c/h4\u003e\n\u003cp\u003e上次洗完牙后,还是不时有出血的情况。\u003c/p\u003e\n\u003cp\u003e前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。\u003c/p\u003e\n\u003cp\u003e于是,又跑到医院来看牙了。\u003c/p\u003e\n\u003cp\u003e医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。\u003c/p\u003e","content_html":"\u003ch4 id=\"病情\"\u003e病情\u003c/h4\u003e\n\u003cp\u003e上次洗完牙后,还是不时有出血的情况。\u003c/p\u003e\n\u003cp\u003e前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。\u003c/p\u003e\n\u003cp\u003e于是,又跑到医院来看牙了。\u003c/p\u003e\n\u003cp\u003e医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。\u003c/p\u003e\n\u003cp\u003e回来查了下,牙周刮治还是挺痛苦的,想着找个专业医院看看再。\u003c/p\u003e\n\u003ch4 id=\"挂号\"\u003e挂号\u003c/h4\u003e\n\u003cp\u003e于是开始北大口腔挂号,中间挂了一次总院的普通号,但是网上好多说是规培生操作,又取消了。\u003c/p\u003e\n\u003cp\u003e找了农行的代挂号服务,北大口腔只能微信预约,他们也没渠道,只能自己挂了。\u003c/p\u003e\n\u003cp\u003e总院实在是挂不到,于是转战第一门诊部的号。\u003c/p\u003e\n\u003cp\u003e功夫不负有心人,7月5日总算挂上了牙周科李菲医生的号。\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003ch4 id=\"治疗\"\u003e治疗\u003c/h4\u003e\n\u003cp\u003e7月7日,第一次看医生,刮治是没的跑了。\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003cp\u003e做了牙齿探针,每个牙都要测一下,略疼。\u003c/p\u003e\n\u003cp\u003e开了治疗计划,刮治分两次,每次半口牙,总费用3000元左右。\u003c/p\u003e\n\u003cp\u003e第一次只洗牙,确实是专业,洗牙不到半个小时搞定。\u003c/p\u003e\n\u003cp\u003e最关键的,洗完后我刷牙确实很少再出血了。\u003c/p\u003e\n\u003cp\u003e8月3日,第一次刮治,只做了右半边。\u003c/p\u003e\n\u003cp\u003e医生说我这算是轻的,刮治比洗牙会疼一些,可以打麻药也可以不打。\u003c/p\u003e\n\u003cp\u003e行吧,那咱就不打麻药,来吧。\u003c/p\u003e\n\u003cp\u003e医生开始操作,感觉和洗牙是一样的,也是拿喷水的机器在牙上弄来弄去。\u003c/p\u003e\n\u003cp\u003e但是比洗牙更深一些,感觉是直接到牙龈下面了,确实要更疼一些。\u003c/p\u003e\n\u003cp\u003e洗完后,再拿一个铁丝一样的东西在牙上使劲刮来刮去。\u003c/p\u003e\n\u003cp\u003e自我感觉,还是前面洗牙更痛苦一些,后面刮来刮去没什么感觉。\u003c/p\u003e\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \u003cfigcaption\u003e\n 铁丝一样的工具,第二次刮治拍的\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003e半个小时搞定,本来我以为我这得一俩小时,医生说那得是非常严重的,你这属于轻的。\u003c/p\u003e\n\u003cp\u003e9月2日,第二次刮治,做了左半边。\u003c/p\u003e\n\u003cp\u003e左边牙龈问题更压重些,这次痛感更强烈一些。\u003c/p\u003e\n\u003cp\u003e同样也是半个小时搞定,1个月后再来复诊。\u003c/p\u003e\n\u003ch4 id=\"费用\"\u003e费用\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e洗牙:480元,单子找不到了,我记得是这个数。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第一次刮治:1286元\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e第二次刮治:1176元\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e总费用2942元,基本都走了医保。\u003c/p\u003e\n","date_published":"2023-09-17T15:46:32+08:00","tags":[]},{"title":"故乡回忆之旅","id":"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/","url":"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/","summary":"\u003cp\u003e赶在8月底,趁着娃暑假的尾声,回了趟老家。\u003c/p\u003e\n\u003cp\u003e老家有条俗语,“永福庄的街,三里长”。\u003c/p\u003e\n\u003cp\u003e这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。\u003c/p\u003e\n\u003cp\u003e小时候,整天在这条街上跑来跑去。\u003c/p\u003e","content_html":"\u003cp\u003e赶在8月底,趁着娃暑假的尾声,回了趟老家。\u003c/p\u003e\n\u003cp\u003e老家有条俗语,“永福庄的街,三里长”。\u003c/p\u003e\n\u003cp\u003e这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。\u003c/p\u003e\n\u003cp\u003e小时候,整天在这条街上跑来跑去。\u003c/p\u003e\n\u003cp\u003e我们那巷子基本很少是堵头的,你可以穿到任意一个巷子,最后还能回到大街。\u003c/p\u003e\n\u003cp\u003e小时候的大街,感觉很宽很长。\u003c/p\u003e\n\u003cp\u003e现在大街都垫高了,大街也变的窄了许多。\u003c/p\u003e\n\u003cp\u003e大街上的人很少,好多店铺都关门了。\u003c/p\u003e\n\u003cp\u003e走在大街上,小时候的一幕幕映在眼前。\u003c/p\u003e\n\u003cp\u003e有的地方,已经拆了重盖;\u003c/p\u003e\n\u003cp\u003e有的地方,已经人去楼空。\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu3006740006649713065.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu4934023691421517793.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu14151708002844463235.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu8434099771930637415.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu14313312843032992940.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210_hu17649131178520809984.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348210.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092348210.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 爷爷待过的大队部\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu6336645708170525226.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu1397528316934953203.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu15618538421707335487.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu13205494613761534942.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu1593567249556602312.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324_hu14037396544662102675.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092348324.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092348324.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 以前的供销社\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu5541540640269891982.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu7037997050479197269.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu145005599180908817.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu731076964394887047.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu15308385156392274254.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308_hu509790328523226346.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350308.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092350308.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 老爷爷家\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu16617486809879436799.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu7432699745329030812.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu7036418364564234182.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu17710070191127292268.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu5729448113798199425.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619_hu2433505532983287836.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092350619.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092350619.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 姥爷以前的药铺\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu15478602443917385830.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu4403035001115536031.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu14597285372421505784.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu2123523744818986231.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu11645023921160621769.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461_hu11836963035739125659.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101306461.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309101306461.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 姥姥家\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu7654230755973072684.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu12686240451327229605.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu7146746786070588117.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu5439968662717085529.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu9366285813139017495.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174_hu2603988060458277865.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101307174.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309101307174.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 最早是肉食店,后来开过药铺\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu122183408365254010.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu11063552035295180075.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu7261077057420717472.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu3072706142550964296.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu8022107772889313497.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503_hu17445641280591727019.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101308503.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309101308503.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 里面以前是个学校\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu469253816186341926.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu6878470649797184229.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu9220987119735129066.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu17315677465720342703.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu7226568527224028820.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674_hu9767249176170565089.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349674.jpg 1280w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092349674.jpg#center\"width=\"1280\"height=\"2276\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 巷口的大饭店\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu14776936020413754579.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu2666303332358173500.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu5328217669090157384.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu7852407319406279843.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu12606325031551251496.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469_hu2597285684635397051.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092349469.jpg 1280w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092349469.jpg#center\"width=\"1280\"height=\"2276\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 一栋风格怪异的楼\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu8321561583722494546.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu7872454228474058102.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu14462402126953649247.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu15516876150097055546.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu1434482351744164694.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506_hu10069595325473335433.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092351506.jpg 1280w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092351506.jpg#center\"width=\"1280\"height=\"2276\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 巷子\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu9410619633719661015.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu2710780007104389899.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu17327165547691344568.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu2435452082129100458.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu2638088975046242217.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743_hu302272236195815730.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352743.jpg 1280w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092352743.jpg#center\"width=\"1280\"height=\"2276\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 刘家祖庙\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu17015132040012874964.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu6307723781029201127.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu3134778518951265071.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu4982306534026316998.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu5664365770069011367.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811_hu10546012771229261709.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092353811.jpg 1280w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092353811.jpg#center\"width=\"1280\"height=\"2276\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 赵家祖庙\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu14014938150088315224.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu987640561682868003.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu11215193266338815767.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu17800906480283189709.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu15866005919592955040.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581_hu2921174638698463898.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309092352581.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309092352581.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 赵家祖庙\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\n\n\n\n\n\u003cfigure class=\"align-center \"\u003e\n \n \u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu16921044953458615157.webp 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu12202480585815608712.webp 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu6714625670964178848.webp 1080w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu8469938580071057749.jpg 480w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu8248244727297415348.jpg 720w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020_hu10709794427116929503.jpg 1080w, https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/202309101309020.jpg 2276w\" sizes=\"(min-width: 768px) 720px, 100vw\" /\u003e\n \u003cimg loading=\"lazy\" src=\"202309101309020.jpg#center\"width=\"2276\"height=\"1280\"/\u003e \n \u003c/picture\u003e\n \u003cfigcaption\u003e\n 关帝庙\n \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/p\u003e\n","date_published":"2023-09-09T23:19:03+08:00","tags":[]},{"title":"解决Golang使用go get安装包后找不到可执行文件的问题","id":"https://liudon.com/posts/golang-go-get-binary-not-found-solution/","url":"https://liudon.com/posts/golang-go-get-binary-not-found-solution/","summary":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e编译流水线代码\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego get google.golang.org/protobuf/cmd/protoc-gen-go@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego build -o xxx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在go升级到1.20.1版本后,执行报错。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc-gen-go: program not found or is not executable\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"解决\"\u003e解决\u003c/h4\u003e\n\u003cblockquote\u003e\n\u003cp\u003eStarting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.\u003c/p\u003e\n\u003cp\u003eIn a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.\u003c/p\u003e","content_html":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e编译流水线代码\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego get google.golang.org/protobuf/cmd/protoc-gen-go@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego build -o xxx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在go升级到1.20.1版本后,执行报错。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotoc-gen-go: program not found or is not executable\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"解决\"\u003e解决\u003c/h4\u003e\n\u003cblockquote\u003e\n\u003cp\u003eStarting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.\u003c/p\u003e\n\u003cp\u003eIn a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://docs.studygolang.com/doc/go-get-install-deprecation\"\u003ehttps://docs.studygolang.com/doc/go-get-install-deprecation\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e从 Go 1.7 版本开始,go get 命令默认只会下载包,不会自动编译和安装可执行文件。\u003c/p\u003e\n\u003cp\u003e因此,如果你想要使用 go get 命令安装包并编译可执行文件,你需要使用 go install 命令。\u003c/p\u003e\n\u003cp\u003e替换为\u003ccode\u003ego install\u003c/code\u003e解决。\u003c/p\u003e\n","date_published":"2023-08-17T09:20:50+08:00","tags":["golang"]},{"title":"修正Hugo的JSON Feed格式","id":"https://liudon.com/posts/fix-hugo-json-feed/","url":"https://liudon.com/posts/fix-hugo-json-feed/","summary":"\u003ch4 id=\"问题背景\"\u003e问题背景\u003c/h4\u003e\n\u003cp\u003e前几天在\u003ca href=\"https://planetics.xyz/\"\u003ePlanet\u003c/a\u003e里follow自己的\u003ca href=\"https://liudon.eth\"\u003eweb3博客\u003c/a\u003e,遇到下面的错误。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp 716w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png 716w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202303251415675.png\" width=\"716\" height=\"544\" alt=\"PlanetFeedError\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。\u003c/p\u003e\n\u003cp\u003e因为我的已经修正没法截图,这里以\u003ca href=\"https://dvel.me/index.json\"\u003edvel的博客\u003c/a\u003e举例,格式类似如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content\u0026#34;: \u0026#34;用 ChatGPT 写一些小脚本真是太方便了。\\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\\n几个来回就可以编写一个能正常使用的油猴脚本:\\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer \u0026amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(1) \u0026amp;gt; td` 选项的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(2) \u0026amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\\\ -H \u0026amp;#39;Content-Type: application/json\u0026amp;#39; \\\\ -H \u0026amp;#39;Authorization: Bearer YOUR_API_KEY\u0026amp;#39; \\\\ -d \u0026amp;#39;{ \u0026amp;#34;model\u0026amp;#34;: \u0026amp;#34;gpt-3.5-turbo\u0026amp;#34;, \u0026amp;#34;messages\u0026amp;#34;: [{\u0026amp;#34;role\u0026amp;#34;: \u0026amp;#34;user\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;: \u0026amp;#34;Say this is a test!\u0026amp;#34;}], \u0026amp;#34;temperature\u0026amp;#34;: 0.7 }\u0026amp;#39; ``` 响应格式为: ``` { \u0026amp;#34;id\u0026amp;#34;:\u0026amp;#34;chatcmpl-abc123\u0026amp;#34;, \u0026amp;#34;object\u0026amp;#34;:\u0026amp;#34;chat.completion\u0026amp;#34;, \u0026amp;#34;created\u0026amp;#34;:1677858242, \u0026amp;#34;model\u0026amp;#34;:\u0026amp;#34;gpt-3.5-turbo-0301\u0026amp;#34;, \u0026amp;#34;usage\u0026amp;#34;:{ \u0026amp;#34;prompt_tokens\u0026amp;#34;:13, \u0026amp;#34;completion_tokens\u0026amp;#34;:7, \u0026amp;#34;total_tokens\u0026amp;#34;:20 }, \u0026amp;#34;choices\u0026amp;#34;:[ { \u0026amp;#34;message\u0026amp;#34;:{ \u0026amp;#34;role\u0026amp;#34;:\u0026amp;#34;assistant\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;:\u0026amp;#34;\\\\n\\\\nThis is a test!\u0026amp;#34; }, \u0026amp;#34;finish_reason\u0026amp;#34;:\u0026amp;#34;stop\u0026amp;#34;, \u0026amp;#34;index\u0026amp;#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\\n效果展示:\\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\\n\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;permalink\u0026#34;: \u0026#34;https://dvel.me/posts/chd-quiz-answer/\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;summary\u0026#34;: \u0026#34;用 ChatGPT 写一些小脚本真是太方便了。\\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\\n几个来回就可以编写一个能正常使用的油猴脚本:\\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer \u0026amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(1) \u0026amp;gt; td` 选项的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(2) \u0026amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\\\ -H \u0026amp;#39;Content-Type: application/json\u0026amp;#39; \\\\ -H \u0026amp;#39;Authorization: Bearer YOUR_API_KEY\u0026amp;#39; \\\\ -d \u0026amp;#39;{ \u0026amp;#34;model\u0026amp;#34;: \u0026amp;#34;gpt-3.5-turbo\u0026amp;#34;, \u0026amp;#34;messages\u0026amp;#34;: [{\u0026amp;#34;role\u0026amp;#34;: \u0026amp;#34;user\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;: \u0026amp;#34;Say this is a test!\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;title\u0026#34;: \u0026#34;CHD 油猴脚本:每日签到自动答题\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e下面是一个\u003ccode\u003eJSON Feed\u003c/code\u003e的示例,详细规范见\u003ca href=\"https://www.jsonfeed.org/\"\u003ejsonfeed.org\u003c/a\u003e。\u003c/p\u003e","content_html":"\u003ch4 id=\"问题背景\"\u003e问题背景\u003c/h4\u003e\n\u003cp\u003e前几天在\u003ca href=\"https://planetics.xyz/\"\u003ePlanet\u003c/a\u003e里follow自己的\u003ca href=\"https://liudon.eth\"\u003eweb3博客\u003c/a\u003e,遇到下面的错误。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp 716w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png 716w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202303251415675.png\" width=\"716\" height=\"544\" alt=\"PlanetFeedError\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。\u003c/p\u003e\n\u003cp\u003e因为我的已经修正没法截图,这里以\u003ca href=\"https://dvel.me/index.json\"\u003edvel的博客\u003c/a\u003e举例,格式类似如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content\u0026#34;: \u0026#34;用 ChatGPT 写一些小脚本真是太方便了。\\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\\n几个来回就可以编写一个能正常使用的油猴脚本:\\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer \u0026amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(1) \u0026amp;gt; td` 选项的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(2) \u0026amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\\\ -H \u0026amp;#39;Content-Type: application/json\u0026amp;#39; \\\\ -H \u0026amp;#39;Authorization: Bearer YOUR_API_KEY\u0026amp;#39; \\\\ -d \u0026amp;#39;{ \u0026amp;#34;model\u0026amp;#34;: \u0026amp;#34;gpt-3.5-turbo\u0026amp;#34;, \u0026amp;#34;messages\u0026amp;#34;: [{\u0026amp;#34;role\u0026amp;#34;: \u0026amp;#34;user\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;: \u0026amp;#34;Say this is a test!\u0026amp;#34;}], \u0026amp;#34;temperature\u0026amp;#34;: 0.7 }\u0026amp;#39; ``` 响应格式为: ``` { \u0026amp;#34;id\u0026amp;#34;:\u0026amp;#34;chatcmpl-abc123\u0026amp;#34;, \u0026amp;#34;object\u0026amp;#34;:\u0026amp;#34;chat.completion\u0026amp;#34;, \u0026amp;#34;created\u0026amp;#34;:1677858242, \u0026amp;#34;model\u0026amp;#34;:\u0026amp;#34;gpt-3.5-turbo-0301\u0026amp;#34;, \u0026amp;#34;usage\u0026amp;#34;:{ \u0026amp;#34;prompt_tokens\u0026amp;#34;:13, \u0026amp;#34;completion_tokens\u0026amp;#34;:7, \u0026amp;#34;total_tokens\u0026amp;#34;:20 }, \u0026amp;#34;choices\u0026amp;#34;:[ { \u0026amp;#34;message\u0026amp;#34;:{ \u0026amp;#34;role\u0026amp;#34;:\u0026amp;#34;assistant\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;:\u0026amp;#34;\\\\n\\\\nThis is a test!\u0026amp;#34; }, \u0026amp;#34;finish_reason\u0026amp;#34;:\u0026amp;#34;stop\u0026amp;#34;, \u0026amp;#34;index\u0026amp;#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\\n效果展示:\\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\\n\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;permalink\u0026#34;: \u0026#34;https://dvel.me/posts/chd-quiz-answer/\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;summary\u0026#34;: \u0026#34;用 ChatGPT 写一些小脚本真是太方便了。\\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\\n几个来回就可以编写一个能正常使用的油猴脚本:\\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer \u0026amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(1) \u0026amp;gt; td` 选项的获取路径是 `#outer \u0026amp;gt; form \u0026amp;gt; table \u0026amp;gt; tbody \u0026amp;gt; tr:nth-child(2) \u0026amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\\\ -H \u0026amp;#39;Content-Type: application/json\u0026amp;#39; \\\\ -H \u0026amp;#39;Authorization: Bearer YOUR_API_KEY\u0026amp;#39; \\\\ -d \u0026amp;#39;{ \u0026amp;#34;model\u0026amp;#34;: \u0026amp;#34;gpt-3.5-turbo\u0026amp;#34;, \u0026amp;#34;messages\u0026amp;#34;: [{\u0026amp;#34;role\u0026amp;#34;: \u0026amp;#34;user\u0026amp;#34;, \u0026amp;#34;content\u0026amp;#34;: \u0026amp;#34;Say this is a test!\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;title\u0026#34;: \u0026#34;CHD 油猴脚本:每日签到自动答题\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e下面是一个\u003ccode\u003eJSON Feed\u003c/code\u003e的示例,详细规范见\u003ca href=\"https://www.jsonfeed.org/\"\u003ejsonfeed.org\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;version\u0026#34;: \u0026#34;https://jsonfeed.org/version/1.1\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;title\u0026#34;: \u0026#34;My Example Feed\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;home_page_url\u0026#34;: \u0026#34;https://example.org/\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;feed_url\u0026#34;: \u0026#34;https://example.org/feed.json\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;items\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content_text\u0026#34;: \u0026#34;This is a second item.\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;url\u0026#34;: \u0026#34;https://example.org/second-item\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content_html\u0026#34;: \u0026#34;\u0026lt;p\u0026gt;Hello, world!\u0026lt;/p\u0026gt;\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;url\u0026#34;: \u0026#34;https://example.org/initial-post\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"修复方案\"\u003e修复方案\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e添加自定义\u003ccode\u003ejsonfeed\u003c/code\u003e模版文件,路径为\u003ccode\u003elayouts/_default/index.jsonfeed.json\u003c/code\u003e。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e文件内容如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $pctx := . -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $pages := slice -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- if or $.IsHome $.IsSection -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $pages = $pctx.RegularPages -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- else -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $pages = $pctx.Pages -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- end -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $limit := site.Config.Services.RSS.Limit -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- if ge $limit 1 -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $pages = $pages | first $limit -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- end -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $title := \u0026#34;\u0026#34; }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- if eq .Title .Site.Title }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $title = .Site.Title }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- else }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- with .Title }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $title = print . \u0026#34; on \u0026#34;}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- $title = print $title .Site.Title }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;version\u0026#34;: \u0026#34;https://jsonfeed.org/version/1.1\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;title\u0026#34;: {{ $title | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;home_page_url\u0026#34;: {{ .Permalink | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- with .OutputFormats.Get \u0026#34;jsonfeed\u0026#34; }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;feed_url\u0026#34;: {{ .Permalink | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if (or .Site.Params.author .Site.Params.author_url) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;authors\u0026#34;: [{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if .Site.Params.author }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;name\u0026#34;: {{ .Site.Params.author | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if .Site.Params.author_url }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;url\u0026#34;: {{ .Site.Params.author_url | jsonify }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if $pages }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;items\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- range $index, $element := $pages }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- with $element }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if $index }},{{end}} {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;title\u0026#34;: {{ .Title | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;id\u0026#34;: {{ .Permalink | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;url\u0026#34;: {{ .Permalink | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if .Site.Params.showFullTextinJSONFeed }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;summary\u0026#34;: {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content_html\u0026#34;: {{ .Content | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- else }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;content_text\u0026#34;: {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if .Params.cover.image }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- $cover := (.Resources.ByType \u0026#34;image\u0026#34;).GetMatch (printf \u0026#34;*%s*\u0026#34; (.Params.cover.image)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- if $cover }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;image\u0026#34;: {{ (path.Join .RelPermalink $cover) | absURL | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;date_published\u0026#34;: {{ .Date.Format \u0026#34;2006-01-02T15:04:05Z07:00\u0026#34; | jsonify }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- $tags := slice -}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ with .Params.tags }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ range . }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ $tags = $tags | append (. | jsonify) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{end}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{end}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;tags\u0026#34;: [{{ delimit $tags \u0026#34;, \u0026#34; }}]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{- end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e开启\u003ccode\u003eJSON Feed\u003c/code\u003e。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e配置文件调整如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eoutputFormats:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e jsonfeed: # 添加jsonfeed输出格式\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e mediaType: application/feed+json\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e baseName: feed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel: alternate\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e isPlainText: true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eoutputs:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e home:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - HTML\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - RSS\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - json # fusejs搜索依赖index.json,不要漏掉这个配置\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - jsonfeed # 开启jsonfeed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eparams:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e showFullTextinJSONFeed: true # jsonfeed开启全文输出\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e参考资料:\u003ca href=\"https://foosel.net/til/how-to-add-json-feed-support-to-hugo/\"\u003eHow to add JSON Feed support to Hugo\u003c/a\u003e\u003c/p\u003e\n","date_published":"2023-03-25T14:11:18+08:00","tags":["hugo"]},{"title":"我的学车之路","id":"https://liudon.com/posts/my-journey-of-learning-to-drive/","url":"https://liudon.com/posts/my-journey-of-learning-to-drive/","summary":"\u003cp\u003e之前在\u003ca href=\"https://liudon.com/posts/review-2022/\"\u003e2022年终总结\u003c/a\u003e提到过,我在练车考驾照。\u003c/p\u003e\n\u003cp\u003e就在昨天,终于拿证了。👏👏👏\u003c/p\u003e\n\u003cp\u003e咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年10月12日,科目一考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年10月22日,科目二模拟驾驶。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年11月13日,科目二第一次上车练习。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年2月4日,年后驾校恢复培训,继续科目二练车。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年2月13日,科目二考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年3月11日,科目三上路练习。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年3月23日,上午科目三考试通过,下午科目四考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e考试的时候,早上遇到临时交通管制,一直到9点40才开考。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e考完回来,班车上的人说又管制不能考了。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e班车拉回驾校,剩下的人中午加班考试。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e终于不用再5点半起床赶班车了。🥱\u003c/p\u003e","content_html":"\u003cp\u003e之前在\u003ca href=\"https://liudon.com/posts/review-2022/\"\u003e2022年终总结\u003c/a\u003e提到过,我在练车考驾照。\u003c/p\u003e\n\u003cp\u003e就在昨天,终于拿证了。👏👏👏\u003c/p\u003e\n\u003cp\u003e咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年10月12日,科目一考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年10月22日,科目二模拟驾驶。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年11月13日,科目二第一次上车练习。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年2月4日,年后驾校恢复培训,继续科目二练车。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年2月13日,科目二考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年3月11日,科目三上路练习。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2023年3月23日,上午科目三考试通过,下午科目四考试通过。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e考试的时候,早上遇到临时交通管制,一直到9点40才开考。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e考完回来,班车上的人说又管制不能考了。\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e班车拉回驾校,剩下的人中午加班考试。\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e终于不用再5点半起床赶班车了。🥱\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/my-journey-of-learning-to-drive/202303242116962_hu12425904158888570193.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/my-journey-of-learning-to-drive/202303242116962_hu370609333924303518.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202303242116962.jpeg\" width=\"1276\" height=\"1702\" alt=\"本本到手啦\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2023-03-24T20:48:24+08:00","tags":["个人技能"]},{"title":"将博客部署到星际文件系统(IPFS)","id":"https://liudon.com/posts/deploy-blog-to-ipfs/","url":"https://liudon.com/posts/deploy-blog-to-ipfs/","summary":"\u003cp\u003e在这篇文章,我将会介绍如何利用\u003ccode\u003eGithub Actions\u003c/code\u003e将\u003ccode\u003ehugo\u003c/code\u003e博客自动部署到\u003ccode\u003eIPFS\u003c/code\u003e上,并通过自定义域名访问\u003ccode\u003eIPFS\u003c/code\u003e上的文件。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。\u003c/p\u003e","content_html":"\u003cp\u003e在这篇文章,我将会介绍如何利用\u003ccode\u003eGithub Actions\u003c/code\u003e将\u003ccode\u003ehugo\u003c/code\u003e博客自动部署到\u003ccode\u003eIPFS\u003c/code\u003e上,并通过自定义域名访问\u003ccode\u003eIPFS\u003c/code\u003e上的文件。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e照惯例,先上演示.\u003ca href=\"https://liudon.xyz\"\u003e访问我的IPFS博客\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e欢迎各位pin我的博客, \u003ccode\u003eipfs pin add /ipns/liudon.xyz\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;https://liudon.xyz\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eI\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHTTP\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edate: Tue, \u003cspan style=\"color:#ae81ff\"\u003e21\u003c/span\u003e Feb \u003cspan style=\"color:#ae81ff\"\u003e2023\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e59\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e18\u003c/span\u003e GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtent\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003etype: text\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ehtml\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003evary: Accept\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eEncoding\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaccess\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econtrol\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eallow\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emethods: GET\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaccess\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econtrol\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eallow\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emethods: GET, POST, OPTIONS\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003elast\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emodified: Tue, \u003cspan style=\"color:#ae81ff\"\u003e21\u003c/span\u003e Feb \u003cspan style=\"color:#ae81ff\"\u003e2023\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e23\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e59\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e18\u003c/span\u003e GMT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003egateway\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ehost: ipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ebank1\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esv15\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epath: \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eipns\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eliudon\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003exyz\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eroots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epop: ipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ebank1\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esv15\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etiming\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eallow\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eorigin: \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaccess\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econtrol\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eallow\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eorigin: \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaccess\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econtrol\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eallow\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eheaders: X\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eRequested\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eWith, \u003cspan style=\"color:#a6e22e\"\u003eRange\u003c/span\u003e, Content\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eRange\u003c/span\u003e, X\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eChunked\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eOutput, X\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eStream\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eOutput\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaccess\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econtrol\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eexpose\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eheaders: Content\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eRange\u003c/span\u003e, X\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eChunked\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eOutput, X\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eStream\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eOutput\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elb\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epop: gateway\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ebank1\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esv15\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eproxy\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecache: MISS\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecf\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecache\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003estatus: DYNAMIC\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ereport\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eto: {\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;endpoints\u0026#34;\u003c/span\u003e:[{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;url\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs\u0026#34;\u003c/span\u003e}],\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;group\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cf-nel\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;max_age\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e604800\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enel: {\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;success_fraction\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;report_to\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;cf-nel\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;max_age\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e604800\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eserver: cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecf\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eray: \u003cspan style=\"color:#ae81ff\"\u003e79\u003c/span\u003ed36f598970531f\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eLAX\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ealt\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esvc: h3\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;:443\u0026#34;\u003c/span\u003e; ma\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e86400\u003c/span\u003e, h3\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e29\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;:443\u0026#34;\u003c/span\u003e; ma\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e86400\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"准备工作\"\u003e准备工作:\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003eCloudflare帐号\u003c/li\u003e\n\u003cli\u003e一台VPS主机,我用到腾讯云lighthouse主机2核2G\u003c/li\u003e\n\u003cli\u003e一个域名\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"方案介绍\"\u003e方案介绍:\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu14186891486412235748.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu2620749431992014705.png 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"deploy-blog-to-ipfs.png\" width=\"800\" height=\"390\" alt=\"实现方案\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.\u003c/li\u003e\n\u003cli\u003e在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.\u003c/li\u003e\n\u003cli\u003e在GitHub上利用ipfs-http-client上传文进到5001端口.\u003c/li\u003e\n\u003cli\u003e绑定域名到IPNS地址,通过域名访问IPFS文件.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"1-部署ipfs服务\"\u003e1. 部署IPFS服务\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e安装kubo,详见\u003ca href=\"https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions\"\u003e官方文档\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etar -xvzf kubo_v0.18.1_linux-amd64.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/install.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/ipfs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/LICENSE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/LICENSE-APACHE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/LICENSE-MIT\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; x kubo/README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd kubo\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo bash install.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Moved ./ipfs to /usr/local/bin\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs --version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; ipfs version 0.18.1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e初始化IPFS\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs init --profile=server\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加到开机启动\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Unit]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDescription=IPFS Daemon\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfter=syslog.target network.target remote-fs.target nss-lookup.target\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Service]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eType=simple\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser=root\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[Install]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWantedBy=multi-user.target\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e注意打开\u003ccode\u003e--enable-namesys-pubsub\u003c/code\u003e参数,不然IPNS更新生效很慢。\u003c/p\u003e\n\u003cp\u003e将上述代码保存到\u003ccode\u003e/usr/lib/systemd/system/ipfs.service\u003c/code\u003e文件.\u003c/p\u003e\n\u003cp\u003e启动进程.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esystemctl start ipfs.service\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e开放端口\u003c/p\u003e\n\u003cp\u003eIPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"2-github-actions配置\"\u003e2. GitHub Actions配置\u003c/h4\u003e\n\u003cp\u003e博客我使用的\u003ccode\u003eHugo\u003c/code\u003e,原有的工作流方案见\u003ca href=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/\"\u003e将博客部署到Cloudflare Pages\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e完整的工作流配置见\u003ca href=\"%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E5%8F%AF%E5%8F%82%E8%80%83%5Bmain.yml%5D(https://github.com/Liudon/liudon.github.io/blob/code/.github/workflows/main.yml)\"\u003emain.yml\u003c/a\u003e。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e添加如下变量到Actions secrets\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSSHKEY VPS主机ssh登陆私钥\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e更新yaml配置文件,添加如下任务.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: Connect to ssh \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e BG\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e timeout\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eminutes: \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;${{ secrets.SSHKEY }}\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e../\u003c/span\u003eprivkey\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e chmod \u003cspan style=\"color:#ae81ff\"\u003e600\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e../\u003c/span\u003eprivkey\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ssh \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eo StrictHostKeyChecking\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eno \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e{{ secrets\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSSHHOST }} \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ei \u003cspan style=\"color:#f92672\"\u003e../\u003c/span\u003eprivkey \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eL \u003cspan style=\"color:#ae81ff\"\u003e5001\u003c/span\u003e:localhost:\u003cspan style=\"color:#ae81ff\"\u003e5001\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003efTN\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e name: ipfs upload\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: aquiladev\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003eipfs\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003emaster\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e id: deploy\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e timeout\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eminutes: \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e with:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e path: \u003cspan style=\"color:#f92672\"\u003e./\u003c/span\u003epublic\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e service: ipfs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e verbose: true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e host: localhost\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e port: \u003cspan style=\"color:#ae81ff\"\u003e5001\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e protocol: http\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e key: self \u003cspan style=\"color:#75715e\"\u003e# 要配置key,这样才会生成IPNS地址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e测试执行action,\u003ca href=\"https://github.com/Liudon/liudon.github.io/actions/runs/4230563492/jobs/7348031553\"\u003e日志\u003c/a\u003e里会有类似如下输出.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUpload to IPFS finished successfully {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecid: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipfs: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eipns: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e每次执行,ipfs地址不同,ipns地址不变.\n记住这里到ipns地址,下面会用到.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"3-域名配置\"\u003e3. 域名配置\u003c/h4\u003e\n\u003cp\u003e在\u003ccode\u003eCloudflare\u003c/code\u003e上添加解析:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e添加DNS TXT记录,名称为\u003ccode\u003e_dnslink\u003c/code\u003e,值为\u003ccode\u003ednslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c\u003c/code\u003e,将这里的\u003ccode\u003e12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c\u003c/code\u003e改为上一步日志里到ipns值。\u003c/li\u003e\n\u003cli\u003e添加DNS CNNANE记录,名称为\u003ccode\u003e你的域名\u003c/code\u003e,值为\u003ccode\u003egateway.ipfs.io\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/dns-record_hu6236716241678277330.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/dns-record_hu2533669715578871390.png 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"dns-record.png\" width=\"800\" height=\"76\" alt=\"DNS解析\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"4-开启相对路径\"\u003e4. 开启相对路径\u003c/h4\u003e\n\u003cp\u003e经\u003ca href=\"https://github.com/livid\"\u003eLivid大佬\u003c/a\u003e提醒,\u003ca href=\"https://zu1k.com/posts/tutorials/p2p/ipfs-easy-use/\"\u003e公共网关访问时存在相对路径问题\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e我用的\u003ccode\u003eHugo\u003c/code\u003e,配置文件里打开\u003ccode\u003erelativeURLs\u003c/code\u003e配置。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erelativeURLs: true\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/20230222080123_hu13211350781374683451.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-ipfs/20230222080123_hu4596934888818164050.png 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230222080123.png\" width=\"800\" height=\"994\" alt=\"VPS主机运行情况\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e两天跑了14G流量,每月的流量资源包基本够用了.\u003c/p\u003e\n\u003cp\u003e参考资料:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://zu1k.com/posts/tutorials/p2p/ipfs-easy-use/\"\u003eIPFS 日用优化指南\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/UnifiedPush/documentation/blob/karmanyaahm-patch-3/config.toml\"\u003e参考配置\u003c/a\u003e\u003c/p\u003e\n","image":"https://liudon.com/posts/deploy-blog-to-ipfs/20230222122431.png","date_published":"2023-02-21T19:46:58+08:00","tags":["hugo","ipfs","github","cloudflare"]},{"title":"新冠疫情后的第一个春节","id":"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/","url":"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/","summary":"\u003cblockquote\u003e\n\u003cp\u003e下面的内容是由chatGPT润色生成的。\u003c/p\u003e\n\u003cp\u003eAI太强大了 😂\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。\u003c/p\u003e\n\u003cp\u003e但我从未想过,等我长大后,我也会成为其中的一员。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003e下面的内容是由chatGPT润色生成的。\u003c/p\u003e\n\u003cp\u003eAI太强大了 😂\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。\u003c/p\u003e\n\u003cp\u003e但我从未想过,等我长大后,我也会成为其中的一员。\u003c/p\u003e\n\u003cp\u003e由于疫情的影响,我已经连续三年不能回家过年了。\u003c/p\u003e\n\u003cp\u003e每次我告诉父母我无法回家,他们总是表现得非常平静,但我不知道他们挂了电话后的心情会如何。\u003c/p\u003e\n\u003cp\u003e直到今年,我们全家都经历了一次感染,但这也使我们有了机会在这个特殊的春节回家过年。\u003c/p\u003e\n\u003cp\u003e提前请了假,带娃体验下老家的生活。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu13773308122635967302.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu16350445115033178451.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210359.jpg\" width=\"1702\" height=\"1276\" alt=\"回家\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e回家啦。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu16297431278291949475.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu169497635061842828.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210413.jpg\" width=\"800\" height=\"800\" alt=\"赶集\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e赶大集。\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003cp\u003e烧火。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu12878139162045151367.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu14322683064006180744.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210418.jpg\" width=\"800\" height=\"1200\" alt=\"放烟花\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e放烟花。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu18175370897587808874.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu8408143810863175133.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210424.jpg\" width=\"800\" height=\"1067\" alt=\"抓鸟\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e抓鸟。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu12697348293945292906.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu4075605898751983642.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210436.jpg\" width=\"800\" height=\"600\" alt=\"放孔明灯\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e放孔明灯。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu17801422576362920580.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu13586914886567271236.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230216210431.jpg\" width=\"800\" height=\"1067\" alt=\"蹭饭\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e邻居家蹭饭。\u003c/p\u003e\n","date_published":"2023-02-16T20:56:38+08:00","tags":[]},{"title":"第一次清理键盘","id":"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/","url":"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/","summary":"\u003cp\u003e19年生日的时候,媳妇送了一款flico的机械键盘。\u003c/p\u003e\n\u003cp\u003e这次搬家后,想着年前清理下键盘,实在是太脏了。\u003c/p\u003e\n\u003cp\u003e周五下班,带上键盘回家。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450523.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e","content_html":"\u003cp\u003e19年生日的时候,媳妇送了一款flico的机械键盘。\u003c/p\u003e\n\u003cp\u003e这次搬家后,想着年前清理下键盘,实在是太脏了。\u003c/p\u003e\n\u003cp\u003e周五下班,带上键盘回家。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450523.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e键盘全貌,上面好多油。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu14958352110478307340.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu9420226909371823587.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450514.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e开拆。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu11173033394433915479.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu1829993996032645083.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450505.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e手还是太慢,上工具吧。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu10633723481158909138.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu5465722458334428985.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450497.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e全部拆完。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu12197024377809750737.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu1230322316630236627.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450488.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e内部特写。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu22231389970031080.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu8998630966370514229.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450480.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e清理出来的灰屑、头发,这键盘见证了我的发迹线变化\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu14483120261100559418.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu12282089114328179317.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450468.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e终于清理干净了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu4699261132071604120.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu5926788081790166205.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161450456.jpeg\" width=\"1706\" height=\"1279\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e复原,又可以咔咔写代码了。\u003c/p\u003e\n","date_published":"2023-01-16T14:43:38+08:00","tags":["自己动手"]},{"title":"2022年终总结","id":"https://liudon.com/posts/review-2022/","url":"https://liudon.com/posts/review-2022/","summary":"\u003cp\u003e2022年已经过去1周多了,记录一下我的2022年。\u003c/p\u003e\n\u003ch4 id=\"疫情\"\u003e疫情\u003c/h4\u003e\n\u003cp\u003e2022年,是新冠疫情的第三年,也是切身感受到的一年。\u003c/p\u003e\n\u003cp\u003e3月22日晚,8点半和同事刚上13号线地铁。\u003c/p\u003e","content_html":"\u003cp\u003e2022年已经过去1周多了,记录一下我的2022年。\u003c/p\u003e\n\u003ch4 id=\"疫情\"\u003e疫情\u003c/h4\u003e\n\u003cp\u003e2022年,是新冠疫情的第三年,也是切身感受到的一年。\u003c/p\u003e\n\u003cp\u003e3月22日晚,8点半和同事刚上13号线地铁。\u003c/p\u003e\n\u003cp\u003e紧接着看到群里有人说,公司大厦因为疫情封控了,只进不出。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/%E5%A4%A7%E5%8E%A6%E5%B0%81%E6%8E%A7_hu8765934698545978203.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/%E5%A4%A7%E5%8E%A6%E5%B0%81%E6%8E%A7_hu17119673248843362961.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%a4%a7%e5%8e%a6%e5%b0%81%e6%8e%a7.jpg\" width=\"800\" height=\"1223\" alt=\"大厦封控\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e第一次感受弹窗3,居家隔离。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/%E5%BC%B9%E7%AA%973_hu9177248844689947037.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/%E5%BC%B9%E7%AA%973_hu4970225532395928247.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%bc%b9%e7%aa%973.jpg\" width=\"800\" height=\"1422\" alt=\"弹窗3\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/%E5%B1%85%E5%AE%B6%E9%9A%94%E7%A6%BB_hu17241506045196841522.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/%E5%B1%85%E5%AE%B6%E9%9A%94%E7%A6%BB_hu9134605477740293062.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%b1%85%e5%ae%b6%e9%9a%94%e7%a6%bb.jpg\" width=\"800\" height=\"1066\" alt=\"居家隔离\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/%E5%B1%85%E5%AE%B6%E8%A7%82%E5%AF%9F_hu8029850890261094517.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/%E5%B1%85%E5%AE%B6%E8%A7%82%E5%AF%9F_hu12702577351484703533.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%b1%85%e5%ae%b6%e8%a7%82%e5%af%9f.jpg\" width=\"800\" height=\"1422\" alt=\"居家观察\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e5月21日,开启居家办公。\u003c/p\u003e\n\u003cp\u003e6月5日,开始到岗工作。\u003c/p\u003e\n\u003cp\u003e11月17日,公司通知第二天居家办公。\u003c/p\u003e\n\u003cp\u003e11月21日,小区通知临时管控。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/%E5%B0%8F%E5%8C%BA%E5%B0%81%E6%8E%A7_hu7494342391696234418.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/%E5%B0%8F%E5%8C%BA%E5%B0%81%E6%8E%A7_hu3236688702478916991.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%b0%8f%e5%8c%ba%e5%b0%81%e6%8e%a7.jpg\" width=\"800\" height=\"1158\" alt=\"小区封控\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e12月10日,媳妇中招。\u003c/p\u003e\n\u003cp\u003e12月12日,自己中招。\u003c/p\u003e\n\u003cp\u003e12月18日,娃中招。\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003cp\u003e12月20日,开始到岗工作,持续了近一个月的居家隔离生活终于结束。\u003c/p\u003e\n\u003cp\u003e老妈冒着北京疫情高峰感染的风险,过来帮我们带娃。\u003c/p\u003e\n\u003cp\u003e从1周一检,到3天一检,不知道健康宝有没有年终报告,告诉你今年做了多少次核酸,相信会是很棒的一个数字。\u003c/p\u003e\n\u003ch4 id=\"入学\"\u003e入学\u003c/h4\u003e\n\u003cp\u003e上半年赶着疫情的间隙,整理好了娃的入学资料。\u003c/p\u003e\n\u003cp\u003e经过一个月的焦虑等待后,最终被附近的学校录取,也确实感受到了离家近的好处。\u003c/p\u003e\n\u003cp\u003e详细经过见\u003ca href=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/\"\u003e记录2022年海淀幼升小\u003c/a\u003e\u003c/p\u003e\n\u003ch4 id=\"休假\"\u003e休假\u003c/h4\u003e\n\u003cp\u003e春节没有回家过年,上半年北京和老家交替出现疫情,最终在8月份休假回了趟家。\u003c/p\u003e\n\u003ch4 id=\"驾照\"\u003e驾照\u003c/h4\u003e\n\u003cp\u003e因为疫情,感觉还是得考个驾照,拖延了N年的事项提上了日程。\u003c/p\u003e\n\u003cp\u003e6月份报名,一直拖到10月份才过的科目一。\u003c/p\u003e\n\u003cp\u003e11月底开始摸车了,刚上2节课因为疫情封校了,现在学的都快忘光了。\u003c/p\u003e\n\u003ch4 id=\"搬家\"\u003e搬家\u003c/h4\u003e\n\u003cp\u003e年底公司通知搬家,又搬回了银科,兜兜转转,又回到了起点。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/20230221153216_hu16647349186155928729.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/review-2022/20230221153216_hu4944469646759786940.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20230221153216.jpg\" width=\"800\" height=\"1067\" alt=\"又回银科\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"博客\"\u003e博客\u003c/h4\u003e\n\u003cp\u003e2022年,把博客又捡了回来,今年多更新吧。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/review-2022/202301161440442_hu6661987352865925613.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/review-2022/202301161440442_hu4648330161098337466.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"202301161440442.png\" width=\"1892\" height=\"1114\" alt=\"\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e或许是上了年纪,2022年发现泪点变得很低,健康、家人才是最重要的。\u003c/p\u003e\n\u003cp\u003e新的一年,继续开源节流,做好防护,保护好家人。\u003c/p\u003e\n\u003cp\u003e最后,祝大家新年快乐!\u003c/p\u003e\n","date_published":"2023-01-12T07:21:20+08:00","tags":["年终总结"]},{"title":"去掉Cloudflare烦人的email-decode.min.js请求","id":"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/","url":"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/","summary":"\u003cp\u003e通过WebPageTest页面测试,发现一个\u003ccode\u003e/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js\u003c/code\u003e的文件请求,影响到了页面渲染。\u003c/p\u003e","content_html":"\u003cp\u003e通过WebPageTest页面测试,发现一个\u003ccode\u003e/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js\u003c/code\u003e的文件请求,影响到了页面渲染。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu17128824884922281865.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu13368857670247743502.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"webpagetest.png\" width=\"1866\" height=\"622\" alt=\"webpagetest测试结果\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e看路径像是Cloudflare的文件,搜了下主题代码,没找到相关文件。\u003c/p\u003e\n\u003cp\u003e经过一番搜索,原来这个是Cloudflare的\u003ccode\u003e电子邮件地址混淆技术\u003c/code\u003e功能。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu1343187000676483553.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu3229372373758113083.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"ScrapeShield.png\" width=\"2512\" height=\"1060\" alt=\"Scrape Shield\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e关闭这个功能即可。\u003c/p\u003e\n","date_published":"2022-08-26T23:25:57+08:00","tags":["Cloudflare"]},{"title":"累计布局偏移修复方案改进 —— 自动生成图片宽高","id":"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/","url":"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/","summary":"\u003cp\u003e本站已不再采用本方案,新方案见\u003ca href=\"/posts/responsive-and-optimized-images-with-hugo/\"\u003e使用Hugo实现响应式和优化的图片\u003c/a\u003e\u003c/p\u003e\n\u003ch4 id=\"遗留的问题\"\u003e遗留的问题\u003c/h4\u003e\n\u003cp\u003e上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e手动输入每张图片的宽高\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。\u003c/p\u003e","content_html":"\u003cp\u003e本站已不再采用本方案,新方案见\u003ca href=\"/posts/responsive-and-optimized-images-with-hugo/\"\u003e使用Hugo实现响应式和优化的图片\u003c/a\u003e\u003c/p\u003e\n\u003ch4 id=\"遗留的问题\"\u003e遗留的问题\u003c/h4\u003e\n\u003cp\u003e上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e手动输入每张图片的宽高\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。\u003c/p\u003e\n\u003cp\u003e身为一名工程师,对于这样一个痛点,势必要优化掉。\u003c/p\u003e\n\u003ch4 id=\"思路\"\u003e思路\u003c/h4\u003e\n\u003cp\u003e发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。\u003c/p\u003e\n\u003cp\u003e要插入的图片代码是类似这样的:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u0026lt; figure src=\u0026#34;https://static.liudon.com/img/cover-code.png\u0026#34; alt=\u0026#34;cover.html code\u0026#34; width=\u0026#34;2020\u0026#34; height=\u0026#34;1468\u0026#34; \u0026gt;}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我使用了\u003ccode\u003epicgo\u003c/code\u003e插件,上传图片到腾讯云对象存储,然后复制\u003ccode\u003emarkdown\u003c/code\u003e图片代码插入文章。\u003c/p\u003e\n\u003cp\u003e能不能通过改造\u003ccode\u003epicgo\u003c/code\u003e插件,将上传后复制的代码,加上图片的宽高参数?\u003c/p\u003e\n\u003cp\u003e通过一番搜索,发现此方案不通,\u003ccode\u003epicgo\u003c/code\u003e确实支持自定义代码,但是变量仅支持\u003ccode\u003e文件名\u003c/code\u003e和\u003ccode\u003eurl\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu8143335939496747907.webp 981w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu7723921126585497891.png 981w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"picgo-config.png\" width=\"981\" height=\"501\" alt=\"picgo config\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e此路不通,只好再想新的办法了。\u003c/p\u003e\n\u003cp\u003e对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见\u003ca href=\"https://cloud.tencent.com/document/product/460/6927\"\u003e获取图片基本信息\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?\u003c/p\u003e\n\u003cp\u003e经过一番搜索,发现\u003ccode\u003ehugo\u003c/code\u003e支持请求url,详细说明见\u003ca href=\"https://gohugo.io/templates/data-templates/#get-remote-data\"\u003eGet Remote Data\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ $dataJ := getJSON \u0026#34;url\u0026#34; }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{ $dataC := getCSV \u0026#34;separator\u0026#34; \u0026#34;url\u0026#34; }}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e哈哈,柳暗花明又一村的感觉。\u003c/p\u003e\n\u003ch4 id=\"解决方案\"\u003e解决方案\u003c/h4\u003e\n\u003cp\u003e此方案基于对象存储获取图片宽高,然后写入图片解析模板。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e新增\u003ccode\u003ecss\u003c/code\u003e配置\u003c/p\u003e\n\u003cp\u003e新增如下配置,否则会导致图片变形。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimg {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e width:\u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e height:auto;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efigure {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e background\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecolor: \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ecode\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ebg);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加\u003ccode\u003erender-image.html\u003c/code\u003e文件\u003c/p\u003e\n\u003cp\u003e代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eitem :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e getJSON \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDestination \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?imageInfo\u0026amp;t=\u0026#34;\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUnix \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e通过对象存储接口获取图片宽高,因为我使用了\u003c/span\u003ecdn\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,所以增加随机数保证拿到最新的图片宽高参数\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eDestination :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eDestination \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eText :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eText \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eTitle :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTitle \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eitem }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Destination | safeURL }}/webp\u0026#34;\u003c/span\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .height }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Destination | safeURL }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $Text }}\u0026#34;\u003c/span\u003e {{ with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eTitle}} title\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e {{ end }} width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .height }}\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加\u003ccode\u003ecover.html\u003c/code\u003e文件\u003c/p\u003e\n\u003cp\u003e代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecxt}} {{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Apply proper context from dict \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage (\u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eisHidden)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealt :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ealt \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecaption \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e plainify) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003efigure \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;entry-cover\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eresponsiveImages) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e default true }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003elinkFullImages (\u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eIsHome)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResources\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eByType \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGetMatch (printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e*\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e i\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ee it is present \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e page bundle \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;noopener noreferrer\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esizes :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (slice \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;360\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;480\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;720\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1080\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1500\u0026#34;\u003c/span\u003e) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessableFormats :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (slice \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;jpg\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;jpeg\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;png\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;tif\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;bmp\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;gif\u0026#34;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e hugo\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsExtended \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessableFormats \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessableFormats \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e append \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;webp\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprod :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (hugo\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eIsProduction \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (eq site\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eenv \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;production\u0026#34;\u003c/span\u003e)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessableFormats \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eMediaType\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eSubType) (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresponsiveImages) (eq \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprod true)) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{- range $size := $sizes -}}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (ge \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esize) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e ((\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eResize (printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ex\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esize))\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePermalink) (printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ew ,\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esize) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ePermalink }} {{printf \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003ew\u0026#34;\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eWidth)}}\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e sizes\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;(min-width: 768px) 720px, 100vw\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Permalink }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $cover.Height }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Unprocessable image \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e responsive images disabled \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e For absolute urls \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e external links, no img processing here \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eitem :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e getJSON \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;?imageInfo\u0026amp;t=\u0026#34;\u003c/span\u003e now\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eUnix \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e通过对象存储接口获取图片宽高,因为我使用了\u003c/span\u003ecdn\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,所以增加随机数保证拿到最新的图片宽高参数\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecoverUrl :\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eimage \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eitem }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $coverUrl | absURL }}\u0026#34;\u003c/span\u003e target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;noopener noreferrer\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $coverUrl | absURL }}/webp\u0026#34;\u003c/span\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .height }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $coverUrl | absURL }}\u0026#34;\u003c/span\u003e width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .width }}\u0026#34;\u003c/span\u003e height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .height }}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e Display Caption \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$.\u003c/span\u003eIsHome }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecaption }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e markdownify }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ep\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003efigure\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e End image \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e End context \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e使用方式和原来不变,插入\u003ccode\u003emarkdown\u003c/code\u003e语法的图片代码即可。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e![picgo config](picgo-config.png)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这下又可以愉快地码字了。\u003c/p\u003e\n","image":"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R.png","date_published":"2022-08-24T12:37:22+08:00","tags":["博客优化","CLS","PagerMod","hugo"]},{"title":"优化博客的累计布局偏移(CLS)问题","id":"https://liudon.com/posts/fix-blog-cls/","url":"https://liudon.com/posts/fix-blog-cls/","summary":"\u003cp\u003e此文已过期,优化方案参考\u003ca href=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/\"\u003e累计布局偏移修复方案改进 —— 自动生成图片宽高\u003c/a\u003e.\u003c/p\u003e\n\u003ch4 id=\"问题表现\"\u003e问题表现\u003c/h4\u003e\n\u003cp\u003e7月份将博客部署由\u003ccode\u003eGithub\u003c/code\u003e迁移到\u003ccode\u003eCloudflare\u003c/code\u003e后,开始关注博客的性能问题。\u003c/p\u003e\n\u003cp\u003e偶然看到苏卡卡大佬的\u003ca href=\"https://blog.skk.moe/post/fix-blog-cls/\"\u003eCLS优化文章\u003c/a\u003e,拿自己博客也测试了下,发现也存在同样的问题。\u003c/p\u003e","content_html":"\u003cp\u003e此文已过期,优化方案参考\u003ca href=\"https://liudon.com/posts/hugo-auto-generate-image-width-and-height/\"\u003e累计布局偏移修复方案改进 —— 自动生成图片宽高\u003c/a\u003e.\u003c/p\u003e\n\u003ch4 id=\"问题表现\"\u003e问题表现\u003c/h4\u003e\n\u003cp\u003e7月份将博客部署由\u003ccode\u003eGithub\u003c/code\u003e迁移到\u003ccode\u003eCloudflare\u003c/code\u003e后,开始关注博客的性能问题。\u003c/p\u003e\n\u003cp\u003e偶然看到苏卡卡大佬的\u003ca href=\"https://blog.skk.moe/post/fix-blog-cls/\"\u003eCLS优化文章\u003c/a\u003e,拿自己博客也测试了下,发现也存在同样的问题。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-blog-cls/lighthouse_result_hu5907100223710448475.webp 1038w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-blog-cls/lighthouse_result_hu3432421370760490344.png 1038w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"lighthouse_result.png\" width=\"1038\" height=\"1144\" alt=\"Lighthouse测试报告\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。\u003c/p\u003e\n\u003cp\u003e为了解决这个问题,需要指定封面的宽高参数。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-blog-cls/cover-code_hu12274468346048023657.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-blog-cls/cover-code_hu1742885001261139749.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"cover-code.png\" width=\"2020\" height=\"1468\" alt=\"cover.html code\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e根据\u003ccode\u003ePagerMod\u003c/code\u003e主题的\u003ccode\u003ecover.html\u003c/code\u003e文件代码,使用绝对地址的情况没有配置宽高参数。\u003c/p\u003e\n\u003ch4 id=\"解决方案\"\u003e解决方案\u003c/h4\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e新增封面配置\u003c/p\u003e\n\u003cp\u003e文章封面配置新增\u003ccode\u003ewidth\u003c/code\u003e和\u003ccode\u003eheight\u003c/code\u003e属性。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecover:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e image: \u0026#34;https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e width: 1620\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e height: 1080\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e自定义封面文件\u003c/p\u003e\n\u003cp\u003e添加自己的\u003ccode\u003ecover.html\u003c/code\u003e文件,核心代码如下,完整代码参考\u003ca href=\"https://github.com/Liudon/liudon.github.io/blob/code/layouts/partials/cover.html\"\u003e我的文件\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e }}{{\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e For absolute urls \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e external links, no img processing here \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eaddLink }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;_blank\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;noopener noreferrer\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.image) | absURL }}/webp\u0026#34;\u003c/span\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewidth) }}width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.width) }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}} {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eheight) }}height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.height) }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ $alt }}\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.image) | absURL }}\u0026#34;\u003c/span\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ewidth) }}width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.width) }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}} {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eParams\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecover\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eheight) }}height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ (.Params.cover.height) }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eimg\u003c/code\u003e标签新增\u003ccode\u003ewidth\u003c/code\u003e和\u003ccode\u003eheight\u003c/code\u003e属性,读取封面配置的\u003ccode\u003ewidth\u003c/code\u003e和\u003ccode\u003eheight\u003c/code\u003e属性值。\u003c/p\u003e\n\u003cp\u003e图片我放到了腾讯云对象存储上,通过图片处理支持了\u003ccode\u003ewebp\u003c/code\u003e格式图片。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-blog-cls/cos-img-process_hu3482055803781631215.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-blog-cls/cos-img-process_hu1983331479586429135.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"cos-img-process.png\" width=\"1590\" height=\"752\" alt=\"cos-img-process\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e新增\u003ccode\u003ecss\u003c/code\u003e配置\u003c/p\u003e\n\u003cp\u003e新增如下配置,否则会导致图片变形。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eimg {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e width:\u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e height:auto;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efigure {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e background\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecolor: \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ecode\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ebg);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"再进一步\"\u003e再进一步\u003c/h4\u003e\n\u003cp\u003e前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。\u003c/p\u003e\n\u003cp\u003e基于\u003ccode\u003emarkdown\u003c/code\u003e语法的图片代码,是不支持宽高参数的。\u003c/p\u003e\n\u003cp\u003e还好\u003ccode\u003ehugo\u003c/code\u003e支持shortcode,其中就有\u003ccode\u003efigure\u003c/code\u003e语法,支持配置宽高参数。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-blog-cls/figure-code_hu17776232354864714128.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-blog-cls/figure-code_hu10475064462256053076.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"figure-code.png\" width=\"2022\" height=\"1336\" alt=\"figure.html code\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e我们使用\u003ccode\u003efigure\u003c/code\u003e语法插入图片,指定图片宽高。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003efigure\u003c/code\u003e解析模板我也进行了改进,类似\u003ccode\u003ecover.html\u003c/code\u003e模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考\u003ca href=\"https://github.com/Liudon/liudon.github.io/blob/code/layouts/shortcodes/figure.html\"\u003e我的文件\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;link\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003ea href\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Get \u0026#34;\u003c/span\u003elink\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}\u0026#34;\u003c/span\u003e{{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;target\u0026#34;\u003c/span\u003e }} target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end }}{{ with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;rel\u0026#34;\u003c/span\u003e }} rel\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end }}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esource type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;image/webp\u0026#34;\u003c/span\u003e srcset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Get \u0026#34;\u003c/span\u003esrc\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}/webp\u0026#34;\u003c/span\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;width\u0026#34;\u003c/span\u003e }} width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;height\u0026#34;\u003c/span\u003e }} height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eimg loading\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ .Get \u0026#34;\u003c/span\u003esrc\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}{{- if eq (.Get \u0026#34;\u003c/span\u003ealign\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;) \u0026#34;\u003c/span\u003ecenter\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}#center{{- end }}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;alt\u0026#34;\u003c/span\u003e) (\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;caption\u0026#34;\u003c/span\u003e) }}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e alt\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ with .Get \u0026#34;\u003c/span\u003ealt\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; }}{{ . }}{{ else }}{{ .Get \u0026#34;\u003c/span\u003ecaption\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; | markdownify| plainify }}{{ end }}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;width\u0026#34;\u003c/span\u003e }} width\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e with \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;height\u0026#34;\u003c/span\u003e }} height\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{{ . }}\u0026#34;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;!--\u003c/span\u003e Closing img tag \u003cspan style=\"color:#f92672\"\u003e--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003epicture\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {{\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eGet \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;link\u0026#34;\u003c/span\u003e }}\u003cspan style=\"color:#f92672\"\u003e\u0026lt;/\u003c/span\u003ea\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e{{ end \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e}}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e使用方式:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e{{\u0026lt; figure src=\u0026ldquo;cover-code.png\u0026rdquo; alt=\u0026ldquo;cover.html code\u0026rdquo; width=\u0026ldquo;2020\u0026rdquo; height=\u0026ldquo;1468\u0026rdquo; \u0026gt;}}\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/fix-blog-cls/gtmetrix-result_hu10689073513855308722.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/fix-blog-cls/gtmetrix-result_hu16088222964268984721.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"gtmetrix-result.png\" width=\"2078\" height=\"758\" alt=\"gtmetrix-result\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。\u003c/p\u003e\n","date_published":"2022-08-20T07:27:22+08:00","tags":["博客优化","CLS","PagerMod"]},{"title":"将博客部署到Cloudflare Pages","id":"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/","url":"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/","summary":"\u003cp\u003e目前博客是部署到了\u003ccode\u003eGithub Pages\u003c/code\u003e上,具体实现见\u003ca href=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/\"\u003e博客架构说明\u003c/a\u003e。\u003c/p\u003e\n\u003ch4 id=\"缘由\"\u003e缘由\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eGithub Pages\u003c/code\u003e部署有一个问题,就是不支持\u003ccode\u003eHSTS\u003c/code\u003e。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。\u003c/p\u003e","content_html":"\u003cp\u003e目前博客是部署到了\u003ccode\u003eGithub Pages\u003c/code\u003e上,具体实现见\u003ca href=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/\"\u003e博客架构说明\u003c/a\u003e。\u003c/p\u003e\n\u003ch4 id=\"缘由\"\u003e缘由\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eGithub Pages\u003c/code\u003e部署有一个问题,就是不支持\u003ccode\u003eHSTS\u003c/code\u003e。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu11689934850592818246.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu8988839328733036755.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220729232638.png\" width=\"1460\" height=\"198\" alt=\"20220729232638\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e虽然\u003ccode\u003eGithub Pages\u003c/code\u003e提供了\u003ccode\u003eEnforce HTTPS\u003c/code\u003e的选项,开启后\u003ccode\u003ehttp\u003c/code\u003e请求会301跳转到\u003ccode\u003ehttps \u003c/code\u003e请求。\u003c/p\u003e\n\u003cp\u003e但是因为返回包缺少\u003ccode\u003eStrict-Transport-Security\u003c/code\u003e的Header头,导致HSTS校验失败。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu14766569725942199582.webp 941w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu2249864527199419434.png 941w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220729234321.png\" width=\"941\" height=\"389\" alt=\"20220729234321\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e为了彻底支持\u003ccode\u003eHSTS\u003c/code\u003e,决定切换到\u003ccode\u003eCloudflare Pages\u003c/code\u003e。\u003c/p\u003e\n\u003ch4 id=\"部署\"\u003e部署\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003eCloudflare Pages\u003c/code\u003e部署非常简单,授权\u003ccode\u003eGithub\u003c/code\u003e仓库权限,配置好分支即可,这里不多介绍。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eGithub Pages\u003c/code\u003e上,\u003ccode\u003ecode\u003c/code\u003e分支保存原始文件,\u003ccode\u003emaster\u003c/code\u003e分支保存\u003ccode\u003ehugo\u003c/code\u003e构建后的文件。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu8654492111814926388.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu13165477065719777794.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220729234310.png\" width=\"1400\" height=\"840\" alt=\"20220729234310\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eCloudflare Pages\u003c/code\u003e这里生成分支选择\u003ccode\u003emaster\u003c/code\u003e,同时禁用其他分支的自动构建。\u003c/p\u003e\n\u003cp\u003e这样提交代码后,\u003ccode\u003egithub actions\u003c/code\u003e构建文件提交到\u003ccode\u003emaster\u003c/code\u003e分支,然后触发\u003ccode\u003eCloudflare Pages\u003c/code\u003e部署。\u003c/p\u003e\n\u003cp\u003e这里为什么没有采用\u003ccode\u003eCloudflare Pages\u003c/code\u003e的自动构建呢?因为很慢,构建一次要3分钟左右。\u003c/p\u003e\n\u003cp\u003e采用拉取\u003ccode\u003emaster\u003c/code\u003e构建好的文件的话,只需要7秒左右。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu4574317858186948276.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu10659876291221712051.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220729234651.png\" width=\"1792\" height=\"610\" alt=\"20220729234651\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"补齐header头\"\u003e补齐Header头\u003c/h4\u003e\n\u003cp\u003e部署好后,\u003ccode\u003eCloudflare Pages\u003c/code\u003e的返回也是没有\u003ccode\u003eStrict-Transport-Security\u003c/code\u003eHeader头的。\u003c/p\u003e\n\u003cp\u003e这里需要通过自定义Header头实现,增加\u003ccode\u003e_headers\u003c/code\u003e文件,内容如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e strict\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003etransport\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003esecurity: max\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eage\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e31536000\u003c/span\u003e; includeSubDomains; preload\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu9149058069851839611.webp 971w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu8433537545901937906.png 971w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220729234942.png\" width=\"971\" height=\"282\" alt=\"20220729234942\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e至此\u003ccode\u003eHSTS\u003c/code\u003e搞定。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Strict-Transport-Security\"\u003eHSTS资料\u003c/a\u003e\u003c/p\u003e\n","date_published":"2022-07-29T23:16:01+08:00","tags":["cloudflare pages","github pages","hugo","cloudflare"]},{"title":"奥林匹克公园向日葵之旅","id":"https://liudon.com/posts/olympic-park-sunflower-tour/","url":"https://liudon.com/posts/olympic-park-sunflower-tour/","summary":"\u003cp\u003e媳妇有事回老家了,这两天自己带娃。\u003c/p\u003e\n\u003cp\u003e小区群里有人说奥林匹克公园的向日葵开了,适合拍照。\u003c/p\u003e\n\u003cp\u003e正好周六多云,没有太阳,出门遛娃。\u003c/p\u003e\n\u003cp\u003e带上我好久不用的相机,省得发霉了。\u003c/p\u003e","content_html":"\u003cp\u003e媳妇有事回老家了,这两天自己带娃。\u003c/p\u003e\n\u003cp\u003e小区群里有人说奥林匹克公园的向日葵开了,适合拍照。\u003c/p\u003e\n\u003cp\u003e正好周六多云,没有太阳,出门遛娃。\u003c/p\u003e\n\u003cp\u003e带上我好久不用的相机,省得发霉了。\u003c/p\u003e\n\u003cp\u003e以往都是去的南园,第一次来北园。\u003c/p\u003e\n\u003cp\u003e西门进入,沿着路往右走,一会就到。\u003c/p\u003e\n\u003cp\u003e人很多,估计大家都因为之前疫情在家憋疯了。\u003c/p\u003e\n\u003cp\u003e到了没多久,太阳又出来了,超级晒。\u003c/p\u003e\n\u003cp\u003e提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817_hu17796839330364625103.webp 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817_hu16491399018090528343.jpg 800w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%be%ae%e4%bf%a1%e5%9b%be%e7%89%87_20220725183817.jpg\" width=\"800\" height=\"533\" alt=\"向日葵\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3492_hu11938434173603406964.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3492_hu15314692271159671672.JPG 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"IMG_3492.JPG\" width=\"1620\" height=\"1080\" alt=\"向日葵\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3556_hu17063267086198247318.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3556_hu16432577379610021050.JPG 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"IMG_3556.JPG\" width=\"1620\" height=\"1080\" alt=\"荷花\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\n\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3549_hu10174327908211758265.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/olympic-park-sunflower-tour/IMG_3549_hu6048251454723091930.JPG 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"IMG_3549.JPG\" width=\"1620\" height=\"1080\" alt=\"荷叶上的蜻蜓\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","image":"https://liudon.com/posts/olympic-park-sunflower-tour/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg","date_published":"2022-07-21T20:40:20+08:00","tags":["带着相机晒太阳","遛娃"]},{"title":"记第二次洗牙","id":"https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/","url":"https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/","summary":"\u003cp\u003e最近刷牙的时候,牙龈总是出血。\u003c/p\u003e\n\u003cp\u003e距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。\u003c/p\u003e\n\u003cp\u003e上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。\u003c/p\u003e","content_html":"\u003cp\u003e最近刷牙的时候,牙龈总是出血。\u003c/p\u003e\n\u003cp\u003e距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。\u003c/p\u003e\n\u003cp\u003e上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。\u003c/p\u003e\n\u003cp\u003e这次决定去医院看看,提前挂了号。\u003c/p\u003e\n\u003cp\u003e医生看过后,说得洗牙,不过当天只能先做洗牙前的检查,洗牙得预约。\u003c/p\u003e\n\u003cp\u003e开单子抽血检查,完事回家。\u003c/p\u003e\n\u003cp\u003e到了预约的日子,约的是4点,3点左右就到医院了。\u003c/p\u003e\n\u003cp\u003e15:57轮到我,进去一共20分钟完事。\u003c/p\u003e\n\u003cp\u003e我以为还有别的事项,结果医生说好了。\u003c/p\u003e\n\u003cp\u003e我说我感觉牙里卡着个东西,医生又拿起装备给我洗了一遍。\u003c/p\u003e\n\u003cp\u003e上次洗牙花了快1个小时,这也太快了。\u003c/p\u003e\n\u003cp\u003e洗牙费用380元,可以走医保报销。\u003c/p\u003e\n\u003cp\u003e以往都说医院洗牙贵,没成想外面私人的要更贵。\u003c/p\u003e\n","date_published":"2022-06-21T21:58:49+08:00","tags":[]},{"title":"记录2022年海淀幼升小","id":"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/","url":"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/","summary":"\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220525202612.png\" width=\"1920\" height=\"2243\" alt=\"20220525202612\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e18年的热点新闻,纳税千万孩子无法在北京上学。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。\u003c/p\u003e","content_html":"\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220525202612.png\" width=\"1920\" height=\"2243\" alt=\"20220525202612\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e18年的热点新闻,纳税千万孩子无法在北京上学。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。\u003c/p\u003e\n\u003cp\u003e提前在网上搜了一番资料,都是一些机构整理的信息。\u003c/p\u003e\n\u003cp\u003e没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。\u003c/p\u003e\n\u003ch3 id=\"1-信息采集\"\u003e1. 信息采集\u003c/h3\u003e\n\u003cp\u003e5月5日,采集系统开放。\u003c/p\u003e\n\u003cp\u003e当天下午录入相关信息,提交网上审核。\u003c/p\u003e\n\u003ch3 id=\"2-网上审核\"\u003e2. 网上审核\u003c/h3\u003e\n\u003cp\u003e信息提交后,就开始了漫长的审核时间。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月06日 户口信息审核通过\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月14日 居住证审核通过\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月16日 居住证明审核通过\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月19日 工作证明审核通过\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202411_hu2325871098114948662.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202411_hu2232900895793906885.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220527202411.png\" width=\"1344\" height=\"850\" alt=\"20220527202411\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch3 id=\"3-线下审核\"\u003e3. 线下审核\u003c/h3\u003e\n\u003cp\u003e网上审核通过后,打印入学申请表,预约线下审核时间。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202458_hu5824582462004341895.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202458_hu9444041697601932939.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220527202458.png\" width=\"2380\" height=\"734\" alt=\"20220527202458\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e这里还有一个插曲,本来以为线下审核是在社区居委会。\u003c/p\u003e\n\u003cp\u003e周六的时候,在小学入学群里聊天,有人说是在学区审核。\u003c/p\u003e\n\u003cp\u003e后来交流一番后,发现是自己理解错了。\u003c/p\u003e\n\u003cp\u003e这里是要先去社区审核盖章,然后再到学区交资料走线下审核。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月22日 社区居委会审核盖章\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5月25日 到街道递交审核材料\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525204053_hu531925169399976859.webp 922w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525204053_hu18367448392582228077.png 922w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220525204053.png\" width=\"922\" height=\"1182\" alt=\"街道审核\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。\u003c/li\u003e\n\u003cli\u003e工作证明还需要提供满足时间要求的社保缴费记录。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"4-审核通过\"\u003e4. 审核通过\u003c/h3\u003e\n\u003cp\u003e5月27日,审核通过后,打印信息采集表。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202545_hu2821024475350553251.webp 868w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220527202545_hu4974026833093093908.png 868w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220527202545.png\" width=\"868\" height=\"488\" alt=\"20220527202545\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch3 id=\"5-学校登记\"\u003e5. 学校登记\u003c/h3\u003e\n\u003cp\u003e6月1日对口学校发布入学登记通知书。\u003c/p\u003e\n\u003cp\u003e按通知书准备资料,到登记时间去学校交资料。\u003c/p\u003e\n\u003cp\u003e今年遇到疫情,改为线上邮件发送资料登记了。\u003c/p\u003e\n\u003ch3 id=\"6-填报志愿\"\u003e6. 填报志愿\u003c/h3\u003e\n\u003cp\u003e6月23日,海淀教育发文\u003ca href=\"https://mp.weixin.qq.com/s/UFoFysrhG2HkzREBpDVkuQ\"\u003e1911后填报志愿通知\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220629215840_hu4201153904083183550.webp 766w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220629215840_hu6710284553326682317.png 766w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220629215840.png\" width=\"766\" height=\"1290\" alt=\"20220629215840\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e第一志愿锁定,其他志愿自己选择填报。\u003c/p\u003e\n\u003cp\u003e6月25日锁定,不允许再修改。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e网上消息,第一志愿锁定,说明有1911后名额,有机会选中。\u003c/p\u003e\n\u003cp\u003e租房的不需要填报志愿,等待派位。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"7-查看结果\"\u003e7. 查看结果\u003c/h3\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220629220638_hu10555322135686406693.webp 310w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220629220638_hu8345186713163492718.png 310w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220629220638.png\" width=\"310\" height=\"298\" alt=\"20220629220638\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e6月29日15点,系统开放结果查询。\u003c/p\u003e\n\u003cp\u003e第一志愿录取,一直担心的调剂没有发生。\u003c/p\u003e\n\u003cp\u003e7月10日,收到教委短信,系统查询录取通知书。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220710085749_hu9769184962070657899.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220710085749_hu17039102873566797094.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220710085749.png\" width=\"1242\" height=\"324\" alt=\"20220710085749\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220710084342_hu3365036360746829129.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220710084342_hu3904377888923366737.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220710084342.png\" width=\"2012\" height=\"836\" alt=\"20220710084342\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e历时1个多月的幼升小总算落地了。\u003c/p\u003e\n","date_published":"2022-05-25T20:10:01+08:00","tags":["幼升小","非京籍","北京幼升小"]},{"title":"Golang解析json的一个问题","id":"https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/","url":"https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/","summary":"\u003cp\u003e业务模块从\u003ccode\u003ephp\u003c/code\u003e迁移到\u003ccode\u003egolang\u003c/code\u003e下了,最近遇到一个golang下json解析的问题:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e请求接口,按返回包字段判断请求成功与否。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e伪代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;encoding/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;code\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;msg\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 场景1,返回包符合接口要求\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;code\u0026#34;:100,\u0026#34;msg\u0026#34;:\u0026#34;failed\u0026#34;}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 解析正确,符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:100 Msg:failed}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 场景2,返回包不符合接口要求,缺少相关字段\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;retCode\u0026#34;:100,\u0026#34;retMsg\u0026#34;:\u0026#34;failed\u0026#34;}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 解析错误,不符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:0 Msg:}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。\u003c/p\u003e","content_html":"\u003cp\u003e业务模块从\u003ccode\u003ephp\u003c/code\u003e迁移到\u003ccode\u003egolang\u003c/code\u003e下了,最近遇到一个golang下json解析的问题:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e请求接口,按返回包字段判断请求成功与否。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e伪代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;encoding/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;code\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;msg\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 场景1,返回包符合接口要求\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;code\u0026#34;:100,\u0026#34;msg\u0026#34;:\u0026#34;failed\u0026#34;}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 解析正确,符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:100 Msg:failed}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 场景2,返回包不符合接口要求,缺少相关字段\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;retCode\u0026#34;:100,\u0026#34;retMsg\u0026#34;:\u0026#34;failed\u0026#34;}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 解析错误,不符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:0 Msg:}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。\u003c/p\u003e\n\u003cp\u003e缺少\u003ccode\u003ecode\u003c/code\u003e字段,\u003ccode\u003eUnmarshal\u003c/code\u003e解析后,按默认值处理,所以\u003ccode\u003ecode\u003c/code\u003e为0,导致验证出错。\u003c/p\u003e\n\u003cp\u003e修正方案:\u003c/p\u003e\n\u003cp\u003e将\u003ccode\u003eCode\u003c/code\u003e字段定义为引用类型,通过判断地址是否为\u003ccode\u003enil\u003c/code\u003e来区分缺少字段的情况。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;encoding/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;fmt\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;code\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;msg\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 返回包符合接口要求\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;code\u0026#34;:100,\u0026#34;msg\u0026#34;:\u0026#34;failed\u0026#34;}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 解析正确,符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:100 Msg:failed}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u0026gt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;request failed\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 返回包不符合接口要求,缺少相关字段\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e = \u003cspan style=\"color:#e6db74\"\u003e`{\u0026#34;id\u0026#34;:1}`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eResponse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUnmarshal\u003c/span\u003e([]byte(\u003cspan style=\"color:#a6e22e\"\u003estr\u003c/span\u003e), \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;res=%+v\\n\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#75715e\"\u003e// 解析错误,不符合预期\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\t\u003cspan style=\"color:#75715e\"\u003e// res={Code:0 Msg:}\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eres1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCode\u003c/span\u003e \u0026gt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efmt\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintln\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;request failed\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e参考资料:\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.calhoun.io/how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided/\"\u003ehow-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://apoorv.blog/json-key-not-set-null-golang/\"\u003ejson-key-not-set-null-golang\u003c/a\u003e\u003c/p\u003e\n","date_published":"2022-05-20T21:18:23+08:00","tags":["golang"]},{"title":"疫情下的生活","id":"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/","url":"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/","summary":"\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220520-192500@2x.png\" width=\"1442\" height=\"924\" alt=\"20220520-192500@2x\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。\u003c/p\u003e\n\u003cp\u003e昨天看新闻,基本没有社会面新增了,感觉要解封了。\u003c/p\u003e\n\u003cp\u003e没想到今天直接被打脸,封控升级了。\u003c/p\u003e","content_html":"\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220520-192500@2x.png\" width=\"1442\" height=\"924\" alt=\"20220520-192500@2x\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。\u003c/p\u003e\n\u003cp\u003e昨天看新闻,基本没有社会面新增了,感觉要解封了。\u003c/p\u003e\n\u003cp\u003e没想到今天直接被打脸,封控升级了。\u003c/p\u003e\n\u003cp\u003e居家办公已经快两周了,也不知道这种日子还要多久。\u003c/p\u003e\n\u003cp\u003e在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。\u003c/p\u003e\n\u003cp\u003e媳妇帮我想了个办法,投屏到电视上。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520194127_hu5158594672106522355.webp 912w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520194127_hu10017315860905738046.png 912w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"20220520194127.png\" width=\"912\" height=\"1202\" alt=\"20220520194127\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。\u003c/p\u003e\n\u003cp\u003e不知道这波还要多久。\u003c/p\u003e\n","date_published":"2022-05-20T19:17:57+08:00","tags":["疫情","北京"]},{"title":"整理下博客的一些调整","id":"https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/","url":"https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/","summary":"\u003cp\u003e新域名上线一段时间了,通过\u003ccode\u003eGoogle Search Console\u003c/code\u003e发现了一些问题,整理下最近进行的一些调整。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e更新主题版本,展示文章tag标签\n通过对比主题作者的网站,发现使用的不是最新代码。\u003c/p\u003e","content_html":"\u003cp\u003e新域名上线一段时间了,通过\u003ccode\u003eGoogle Search Console\u003c/code\u003e发现了一些问题,整理下最近进行的一些调整。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e更新主题版本,展示文章tag标签\n通过对比主题作者的网站,发现使用的不是最新代码。\u003c/p\u003e\n\u003cp\u003e通过调整\u003ccode\u003eGithub Actions\u003c/code\u003e命令解决:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e- name: Checkout repository\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e uses: actions/checkout@v2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e - name: Checkout submodules\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e run: git submodule update --init --recursive --remote\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e修正404页面不生效的问题\n主题是自带了404.html文件的,但是部署后没有生成对应文件。\u003c/p\u003e\n\u003cp\u003e修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。\u003c/p\u003e\n\u003cp\u003e可参考\u003ca href=\"https://github.com/Liudon/liudon.github.io/blob/code/layouts/404.html\"\u003e文件代码\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e两个域名导致的页面权重问题\n发现有些页面\u003ccode\u003eliudon.xyz\u003c/code\u003e收录后,\u003ccode\u003eliudon.com\u003c/code\u003e就不再收录。\u003c/p\u003e\n\u003cp\u003e为了规避这种收录问题,将\u003ccode\u003eliudon.xyz\u003c/code\u003e直接301到了\u003ccode\u003eliudon.com\u003c/code\u003e上。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e目前已调整完毕,观察后续收录情况。\u003c/p\u003e\n","date_published":"2022-05-13T18:20:52+08:00","tags":["博客","收录"]},{"title":"疫情下的五一假期","id":"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/","url":"https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/","summary":"\u003cp\u003e五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。\u003c/p\u003e\n\u003cp\u003e当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。\u003c/p\u003e\n\u003cp\u003e说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。\u003c/p\u003e","content_html":"\u003cp\u003e五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。\u003c/p\u003e\n\u003cp\u003e当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。\u003c/p\u003e\n\u003cp\u003e说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。\u003c/p\u003e\n\u003cp\u003e毕竟老话说的好,手中有粮,心里不慌。\u003c/p\u003e\n\u003cp\u003e第二天出去做核酸,特意去了趟超市,想着再买点肉,结果超市也是各种采购,东西都没了,空手而归。\u003c/p\u003e\n\u003cp\u003e工作上五一前有两个大版本的功能要发布,提前1周需求才提,节前这一周忙的要死,还赶上了疫情,好在赶在发布截止日总算发出去了。\u003c/p\u003e\n\u003cp\u003e就五一当天带娃去了家旁边的公园遛了下,这次轮滑滑的不错,一直玩到晚上7点才回家。\u003c/p\u003e\n\u003cp\u003e夏天到了,北京的杨絮、柳絮各种满天飞,出门真是受罪,其余时间都在家呆着了,偶尔下楼在小区玩会。\u003c/p\u003e\n\u003cp\u003e这个假期基本上每天核酸,感觉以后要常态化核酸了。\u003c/p\u003e\n\u003cp\u003e老家在假期里突然也疫情又起,县城整个封控了,不过老爸还是回到家了,过程稍微费了点劲。\u003c/p\u003e\n\u003cp\u003e突然发现今年的疫情貌似就没消停过,去年五一假期的时候在老家烧烤,今年还不知道什么时候能回家。\u003c/p\u003e\n\u003cp\u003e这疫情什么时候是个头啊……\u003c/p\u003e\n","date_published":"2022-05-05T20:22:23+08:00","tags":["疫情"]},{"title":"自己动手,更换thinkpad x1硬盘","id":"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/","url":"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/","summary":"\u003cp\u003e电脑突然没法用了,提示\u0026quot;A disk read error occurred\u0026quot;的错误。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"491650584526_.pic.jpg\" width=\"1702\" height=\"1276\" alt=\"491650584526_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e多次重启也不行,感觉是硬盘挂了。\u003c/p\u003e\n\u003cp\u003e机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。\u003c/p\u003e","content_html":"\u003cp\u003e电脑突然没法用了,提示\u0026quot;A disk read error occurred\u0026quot;的错误。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"491650584526_.pic.jpg\" width=\"1702\" height=\"1276\" alt=\"491650584526_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e多次重启也不行,感觉是硬盘挂了。\u003c/p\u003e\n\u003cp\u003e机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/WechatIMG64_hu8366303833935516476.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/WechatIMG64_hu10257655814848621050.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG64.jpeg\" width=\"1280\" height=\"1480\" alt=\"WechatIMG64\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e一共就5个螺丝,打开后盖。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/621650757850_.pic_hu13531142737361007931.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/621650757850_.pic_hu13500467035088147851.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"621650757850_.pic.jpg\" width=\"1279\" height=\"1480\" alt=\"621650757850_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e可以看到电池旁边的螺丝,拧下取出电池。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/631650757850_.pic_hu14733579810193593304.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/631650757850_.pic_hu7214629230934141490.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"631650757850_.pic.jpg\" width=\"1280\" height=\"1706\" alt=\"631650757850_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e拧开硬盘上的固定螺丝,取出硬盘。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/541650584531_.pic_hu15048774124751003355.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/541650584531_.pic_hu16468172364107356489.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"541650584531_.pic.jpg\" width=\"1276\" height=\"1702\" alt=\"541650584531_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/WechatIMG55_hu1688270271605025052.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/WechatIMG55_hu15693980055455728109.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG55.jpeg\" width=\"1276\" height=\"1702\" alt=\"WechatIMG55\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e我在京东买的\u003ca href=\"https://u.jd.com/Zdch2qO\"\u003e三星970 EVO Plus\u003c/a\u003e这款,顺利换上。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/611650757849_.pic_hu12516063157909667316.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/611650757849_.pic_hu18445591559889898138.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"611650757849_.pic.jpg\" width=\"1276\" height=\"1702\" alt=\"611650757849_.pic\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e重装系统,搞定,省了一笔,好开心。\u003c/p\u003e\n","date_published":"2022-04-22T08:04:18+08:00","tags":["thinkpad","SSD","硬盘","三星"]},{"title":"二刷百望山","id":"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/","url":"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/","summary":"\u003cp\u003e又是周末,娃约了小伙伴一起爬山。\u003c/p\u003e\n\u003cp\u003e百望山,二刷走起。\u003c/p\u003e\n\u003cp\u003e约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。\u003c/p\u003e\n\u003cp\u003e出门晚了,还打不到车,快10点才到。\u003c/p\u003e\n\u003cp\u003e小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。\u003c/p\u003e","content_html":"\u003cp\u003e又是周末,娃约了小伙伴一起爬山。\u003c/p\u003e\n\u003cp\u003e百望山,二刷走起。\u003c/p\u003e\n\u003cp\u003e约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。\u003c/p\u003e\n\u003cp\u003e出门晚了,还打不到车,快10点才到。\u003c/p\u003e\n\u003cp\u003e小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。\u003c/p\u003e\n\u003cp\u003e继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。\u003c/p\u003e\n\u003cp\u003e这次来,山上明显绿了,花开的更多了,人比上次少多了。\u003c/p\u003e\n\u003cp\u003e下山后,又一起吃了个饭,自助吃的有点撑。\u003c/p\u003e\n\u003cp\u003e今天天气真好,视野相当不错,就是太晒了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG48_hu1172572908314036841.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG48_hu14949915704554245906.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG48.jpeg\" width=\"1702\" height=\"1276\" alt=\"俯瞰北京\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG47_hu8797114940761609245.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG47_hu1076955541246434091.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG47.jpeg\" width=\"1276\" height=\"1702\" alt=\"不知名的树\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG46_hu14337547491232210022.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/WechatIMG46_hu1031091904409366834.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG46.jpeg\" width=\"1702\" height=\"1276\" alt=\"山顶\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2022-04-17T22:57:41+08:00","tags":["爬山","遛娃"]},{"title":"带娃游颐和园","id":"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/","url":"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/","summary":"\u003cp\u003e上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。\u003c/p\u003e\n\u003cp\u003e周六7点准时起床,得早点去省得人多排队。\u003c/p\u003e\n\u003cp\u003e8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。\u003c/p\u003e","content_html":"\u003cp\u003e上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。\u003c/p\u003e\n\u003cp\u003e周六7点准时起床,得早点去省得人多排队。\u003c/p\u003e\n\u003cp\u003e8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。\u003c/p\u003e\n\u003cp\u003e路上不堵,40多分钟到站。\u003c/p\u003e\n\u003cp\u003e进园先去码头排队,人不太多,等了有十几分钟,终于轮到了。\u003c/p\u003e\n\u003cp\u003e总算开上了船,不过天气不太好,灰蒙蒙的。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG40_hu2651074913868480496.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG40_hu14606740751763050506.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG40.jpeg\" width=\"1620\" height=\"1080\" alt=\"终于坐上了船\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e水上荡了1个小时,下船向佛香阁进发。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG41_hu18101397133616820375.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG41_hu1751030110810078032.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG41.jpeg\" width=\"1080\" height=\"1620\" alt=\"佛香阁\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e一路向上,终于登顶,园内风景一览无余。\u003c/p\u003e\n\u003cp\u003e下来后,又去逛了十七孔桥,逛了下湖心小岛。\u003c/p\u003e\n\u003cp\u003e回家又特意坐的双层公交,第一排观光区,哈哈。\u003c/p\u003e\n\u003cp\u003e一天下来1w多步,娃估计得更多,到家直接累摊。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG42_hu18186140578598785649.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG42_hu14106534961347157697.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG42.jpeg\" width=\"1620\" height=\"1080\" alt=\"这是鸳鸯吧\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG43_hu8329383171754737012.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG43_hu15577018806750825269.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG43.jpeg\" width=\"1080\" height=\"1620\" alt=\"佛香阁\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG44_hu10264368041309766573.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/WechatIMG44_hu7961631850814716237.jpeg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WechatIMG44.jpeg\" width=\"1080\" height=\"1620\" alt=\"十七孔桥\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2022-04-11T00:37:55+08:00","tags":[]},{"title":"博客架构说明","id":"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/","url":"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/","summary":"\u003cp\u003e在拿到\u003ccode\u003eliudon.com\u003c/code\u003e域名前,手中已有两个域名:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eliudon.org\u003c/li\u003e\n\u003cli\u003eliudon.xyz\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu10108568587195088134.webp 653w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu6151068063620748445.png 653w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%8d%9a%e5%ae%a2%e6%9e%b6%e6%9e%84.png\" width=\"653\" height=\"206\" alt=\"两套域名说明\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eliudon.org\u003c/code\u003e已经不再更新,仅作归档使用。\n\u003ccode\u003eliudon.xyz\u003c/code\u003e当时是静态博客流行,尝鲜使用。\u003c/p\u003e","content_html":"\u003cp\u003e在拿到\u003ccode\u003eliudon.com\u003c/code\u003e域名前,手中已有两个域名:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eliudon.org\u003c/li\u003e\n\u003cli\u003eliudon.xyz\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu10108568587195088134.webp 653w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu6151068063620748445.png 653w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%8d%9a%e5%ae%a2%e6%9e%b6%e6%9e%84.png\" width=\"653\" height=\"206\" alt=\"两套域名说明\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eliudon.org\u003c/code\u003e已经不再更新,仅作归档使用。\n\u003ccode\u003eliudon.xyz\u003c/code\u003e当时是静态博客流行,尝鲜使用。\u003c/p\u003e\n\u003cp\u003e拿到\u003ccode\u003eliudon.com\u003c/code\u003e域名后,怎么部署博客成了个问题。\u003c/p\u003e\n\u003cp\u003e因为\u003ccode\u003egithub pages\u003c/code\u003e只能绑定一个自定义域名,当然可以通过创建另外一个项目,实现两套域名,但是同一个博客两个项目总感觉不太优雅。\u003c/p\u003e\n\u003cp\u003e经过一番资料查找,终于有了下面这套方案。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%84%E5%BB%BA%E6%B5%81%E7%A8%8B_hu5498963674612948386.webp 1037w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%84%E5%BB%BA%E6%B5%81%E7%A8%8B_hu2580045572028185263.png 1037w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"%e5%8d%9a%e5%ae%a2%e6%9e%84%e5%bb%ba%e6%b5%81%e7%a8%8b.png\" width=\"1037\" height=\"260\" alt=\"博客构建流程\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e通过\u003ccode\u003egithub actions\u003c/code\u003e 和 \u003ccode\u003enetlify\u003c/code\u003e 部署了两套自动化部署方案:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003egithub actions\u003c/code\u003e部署到\u003ccode\u003egithub pages\u003c/code\u003e,绑定自定义域名\u003ccode\u003eliudon.com\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003enetlify\u003c/code\u003e部署到\u003ccode\u003eipfs\u003c/code\u003e,通过\u003ccode\u003ecloudfare ipfs gateway\u003c/code\u003e解析到\u003ccode\u003eipfs\u003c/code\u003e资源,绑定自定义域名\u003ccode\u003eliudon.xyz\u003c/code\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这里要说明一下,\u003ccode\u003eipfs\u003c/code\u003e目前访问延迟较大,这里仅作尝鲜使用。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehugo\u003c/code\u003e的\u003ccode\u003econfig.toml\u003c/code\u003e定义了网站域名,这里为了区分两套域名,在\u003ccode\u003enetlify\u003c/code\u003e部署时,对配置文件做了修改,保证两套域名访问各自页面,具体可参考\u003ca href=\"https://github.com/Liudon/liudon.github.io/blob/code/netlify.toml\"\u003egithub文件内容\u003c/a\u003e。\u003c/p\u003e\n","date_published":"2022-04-10T20:41:57+08:00","tags":[]},{"title":"难得的清明假期","id":"https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/","url":"https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/","summary":"\u003cp\u003e前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。\u003c/p\u003e\n\u003cp\u003e趁着这次难得的假期,外出放松一下。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e爬百望山。\u003c/p\u003e\n\u003cp\u003e娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。\u003c/p\u003e","content_html":"\u003cp\u003e前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。\u003c/p\u003e\n\u003cp\u003e趁着这次难得的假期,外出放松一下。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e爬百望山。\u003c/p\u003e\n\u003cp\u003e娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。\u003c/p\u003e\n\u003cp\u003e进园后,选择大路往上爬,中间走了一段山间台阶路。\u003c/p\u003e\n\u003cp\u003e一路走走停停,补充能量,估计有半个小时,我们就登顶了。\u003c/p\u003e\n\u003cp\u003e山上风有点大,视野不错,可以直接看到互联网的核心公司。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e游北海公园\u003c/p\u003e\n\u003cp\u003e本来是想着去公园划船,结果到了之后发现排队的人太多了,于是放弃,改为游白塔。\u003c/p\u003e\n\u003cp\u003e我们是从北门进的园,然后一路向西去码头打算划船,绕着园子继续往南走。\u003c/p\u003e\n\u003cp\u003e一路走到了南门,心想这出去了还咋去白塔,难道要再走回去?\u003c/p\u003e\n\u003cp\u003e问了门口保安,说是可以出去往东走,从南门再进去。\u003c/p\u003e\n\u003cp\u003e进去后,要爬好几层楼梯才能上到白塔平台,一览园内风景。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/IMG39_hu5924788240665826760.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/png\" srcset=\"https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/IMG39_hu13659472189117285021.png 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"IMG39.png\" width=\"1706\" height=\"1279\" alt=\"北海白塔\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e希望疫情尽快散去,恢复到正常的生活。\u003c/p\u003e\n","date_published":"2022-04-06T08:06:19+08:00","tags":[]},{"title":"十一年的等待,终于拿到了liudon.com域名","id":"https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/","url":"https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/","summary":"\u003cp\u003e在\u003ca href=\"https://liudon.com/about\"\u003e关于\u003c/a\u003e部分,有写域名的来历。\u003c/p\u003e\n\u003cp\u003e当时\u003ccode\u003eliudon.com\u003c/code\u003e已经被注册,所以只好注册了\u003ccode\u003eliudon.org\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e2011年注册的\u003ccode\u003eliudon.org\u003c/code\u003e,最早用\u003ccode\u003ewordpress\u003c/code\u003e搭建了博客。\u003c/p\u003e","content_html":"\u003cp\u003e在\u003ca href=\"https://liudon.com/about\"\u003e关于\u003c/a\u003e部分,有写域名的来历。\u003c/p\u003e\n\u003cp\u003e当时\u003ccode\u003eliudon.com\u003c/code\u003e已经被注册,所以只好注册了\u003ccode\u003eliudon.org\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e2011年注册的\u003ccode\u003eliudon.org\u003c/code\u003e,最早用\u003ccode\u003ewordpress\u003c/code\u003e搭建了博客。\u003c/p\u003e\n\u003cp\u003e这是当时的第一篇文章,\u003ca href=\"https://liudon.org/1.html\"\u003eHello world\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e从2011年,到2019年,中间断断续续的在更新着,博客程序也从\u003ccode\u003ewordpress\u003c/code\u003e转到了\u003ccode\u003etypecho\u003c/code\u003e,再后来开始流行静态页博客,又转到了hugo下。\u003c/p\u003e\n\u003cp\u003e在这中间,一直想要拿到\u003ccode\u003eliudon.com\u003c/code\u003e域名。\u003c/p\u003e\n\u003cp\u003e看网站内容,应该是个德国乐队的网站,这里能看到网站\u003ca href=\"http://web.archive.org/web/20210621212816/http://www.liudon.com/\"\u003e最后的信息\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e3月10日的时候,偶然看到群里有在说域名,于是又搜索了一番,发现域名上了阿里云域名拍卖,购买的时候提示卖家已下家。\u003c/p\u003e\n\u003cp\u003e到国外域名商上查了一下,\u003ccode\u003egodaddy\u003c/code\u003e提供代购,\u003ccode\u003ename\u003c/code\u003e提示为高级域名,可以购买。\u003c/p\u003e\n\u003cp\u003e看\u003ccode\u003earchive.org\u003c/code\u003e上的信息,2021年11月的时候服务不可用了,不知道发生了什么。\u003c/p\u003e\n\u003cp\u003e机不可失,时不再来,立马下单。\u003c/p\u003e\n\u003cp\u003e接下来就是漫长的等待了,系统显示需要5-10个工作日才能完成交易。\u003c/p\u003e\n\u003cp\u003e同时我发现他们服务条款写着,高级域名不支持退款,whois信息一直不变,担心打水漂。\u003c/p\u003e\n\u003cp\u003e期间有一天晚上11点联系客户,说是已经在加快处理。\u003c/p\u003e\n\u003cp\u003e过了10天后,交易还没完成,有点放弃了。\u003c/p\u003e\n\u003cp\u003e3月24日晚上,再次查whois,发现信息有变,登录name,发现订单已完成。\u003c/p\u003e\n\u003cp\u003e现在这个域名终于属于我了。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eliudon.org 老站,备份,不再更新\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eliudon.com -\u0026gt; github pages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eliudon.xyz -\u0026gt; netfily -\u0026gt; ipfs\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","date_published":"2022-04-01T01:24:05+08:00","tags":[]},{"title":"被隔离的一周","id":"https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/","url":"https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/","summary":"\u003cp\u003e从没有想过疫情会离自己这么近,记录一下。\u003c/p\u003e\n\u003cp\u003e周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。\u003c/p\u003e\n\u003cp\u003e周二早上全员核酸阴性,继续到公司上班。\u003c/p\u003e","content_html":"\u003cp\u003e从没有想过疫情会离自己这么近,记录一下。\u003c/p\u003e\n\u003cp\u003e周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。\u003c/p\u003e\n\u003cp\u003e周二早上全员核酸阴性,继续到公司上班。\u003c/p\u003e\n\u003cp\u003e晚上8点10分刚和同事上地铁,部门群里有人说我们楼有人确诊,过了10分钟,说是大楼管控了,不让出楼了。\u003c/p\u003e\n\u003cp\u003e封在楼里的同学统一做核酸,结果出来后才能回家,好多人在公司过了夜。\u003c/p\u003e\n\u003cp\u003e周三到公司集体核酸,做完继续居家办公,今天开始公安局、社区开始联系确认信息,公司要求所有人员居家办公,社区反馈需要居家隔离。\u003c/p\u003e\n\u003cp\u003e头几天还说对门上封条了,没成想这次轮到自己了。\u003c/p\u003e\n\u003cp\u003e到03/31日,隔离解除,健康宝恢复正常。\u003c/p\u003e\n\u003cp\u003e第一次体验到健康宝显示弹窗,然后又显示居家隔离。\u003c/p\u003e\n\u003cp\u003e第一次体验到被贴封条上门磁。\u003c/p\u003e\n\u003cp\u003e第一次体验到鼻拭子,尤其解封前最后一次核酸,双鼻孔。\u003c/p\u003e\n","date_published":"2022-04-01T01:20:39+08:00","tags":["疫情"]},{"title":"mysql中字符串和整型自动转换的问题","id":"https://liudon.com/posts/strings-and-numbers-in-mysql/","url":"https://liudon.com/posts/strings-and-numbers-in-mysql/","summary":"\u003cp\u003e表结构如下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edesc info;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Field \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Type \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Null \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Key \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Default \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Extra \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e id \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e) unsigned \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NO \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e PRI \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NULL \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e auto_increment \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e varchar(\u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e YES \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NULL \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e rows \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e set (\u003cspan style=\"color:#ae81ff\"\u003e0.00\u003c/span\u003e sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行sql.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003einsert into info values (\u0026#39;\u0026#39;, \u0026#39;xxx\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003einsert into info values (\u0026#39;\u0026#39;, \u0026#39;yyy\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e查询记录.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| id | name |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 1 | xxx |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 2 | yyy |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行下面sql.\u003c/p\u003e","content_html":"\u003cp\u003e表结构如下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edesc info;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Field \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Type \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Null \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Key \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Default \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e Extra \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e id \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e8\u003c/span\u003e) unsigned \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NO \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e PRI \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NULL \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e auto_increment \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e varchar(\u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e YES \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e NULL \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e+-------+-----------------+------+-----+---------+----------------+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e rows \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e set (\u003cspan style=\"color:#ae81ff\"\u003e0.00\u003c/span\u003e sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行sql.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003einsert into info values (\u0026#39;\u0026#39;, \u0026#39;xxx\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003einsert into info values (\u0026#39;\u0026#39;, \u0026#39;yyy\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e查询记录.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| id | name |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 1 | xxx |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 2 | yyy |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行下面sql.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info where id = 1;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info where id = \u0026#39;1aaaa\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e你先想想结果会是什么。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info where id = 1;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| id | name |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 1 | xxx |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 row in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eselect * from info where id = \u0026#39;1aaaa\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| id | name |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 1 | xxx |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----+------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 row in set, 1 warning (0.01 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e两个sql都查到了id = 1的记录,唯一的区别在于第二个sql有一个warning错误。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eshow warnings;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+---------+------+-------------------------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| Level | Code | Message |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+---------+------+-------------------------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| Warning | 1292 | Truncated incorrect DOUBLE value: \u0026#39;1aaaa\u0026#39; |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+---------+------+-------------------------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 row in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003emysql在查询时,会根据字段类型进行转换,这里\u003ccode\u003e1aaaa\u003c/code\u003e被转为了\u003ccode\u003e1\u003c/code\u003e。\u003c/p\u003e\n","date_published":"2020-12-14T18:47:29+08:00","tags":["mysql"]},{"title":"一次惊心动魄的Mysql更新操作","id":"https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/","url":"https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/","summary":"\u003ch4 id=\"问题描述\"\u003e问题描述\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 表结构\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; desc user_packages;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| Field | Type | Null | Key | Default | Extra |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| start_date | date | NO | | NULL | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| end_date | date | NO | | NULL | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_updated | datetime | NO | | 0000-00-00 00:00:00 | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; select * from user_packages limit 5;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | start_date | end_date |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 185 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 186 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 187 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 188 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 189 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"操作过程\"\u003e操作过程\u003c/h4\u003e\n\u003cp\u003e需要更新某条记录的\u003ccode\u003eend_date\u003c/code\u003e字段,执行sql如下:\u003c/p\u003e","content_html":"\u003ch4 id=\"问题描述\"\u003e问题描述\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# 表结构\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; desc user_packages;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| Field | Type | Null | Key | Default | Extra |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| start_date | date | NO | | NULL | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| end_date | date | NO | | NULL | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_updated | datetime | NO | | 0000-00-00 00:00:00 | |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+----------------+---------------------+------+-----+---------------------+----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; select * from user_packages limit 5;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | start_date | end_date |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 185 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 186 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 187 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 188 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 189 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e5 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"操作过程\"\u003e操作过程\u003c/h4\u003e\n\u003cp\u003e需要更新某条记录的\u003ccode\u003eend_date\u003c/code\u003e字段,执行sql如下:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; update user_packages set end_date = \u0026#39;2020-06-06\u0026#39; and up_id = 189 limit 1;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eQuery OK, 1 row affected, 1 warning (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eRows matched: 1 Changed: 1 Warnings: 1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行完,发现sql写错了!!!!\u003c/p\u003e\n\u003cp\u003e正确的sql应该是:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eupdate user_packages set end_date = \u0026#39;2020-06-06\u0026#39; where up_id = 189 limit 1;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e误把\u003ccode\u003ewhere\u003c/code\u003e写成了\u003ccode\u003eand\u003c/code\u003e,还好指定了\u003ccode\u003elimit = 1\u003c/code\u003e,只操作了一条记录。\u003c/p\u003e\n\u003ch4 id=\"回滚\"\u003e回滚\u003c/h4\u003e\n\u003cp\u003e回滚的前提,要先找到更新的那条记录。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eup_id\u003c/code\u003e为表的主键,更新前表里已经有这条记录了,主键不能重复,感觉语句应该没有执行成功。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; select * from user_packages where up_id = 189;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | start_date | end_date |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 189 | 2018-04-01 | 2018-06-30 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行查询语句,表里确实也只有这一条\u003ccode\u003eup_id=189\u003c/code\u003e的记录。\u003c/p\u003e\n\u003cp\u003e感觉这个\u003ccode\u003eupdate\u003c/code\u003e语句应该没执行成功,但是没执行成功应该报错的呀。\u003c/p\u003e\n\u003cp\u003e这个时候把希望放到了语句结果里的\u003ccode\u003eWarnings: 1\u003c/code\u003e,是不是没执行成功呢。\u003c/p\u003e\n\u003cp\u003e因为紧接着又执行了其他语句,所以也无法通过\u003ccode\u003eshow warnings\u003c/code\u003e查看具体的错误信息了。\u003c/p\u003e\n\u003cp\u003e那么这条语句到底执行成功了吗?如果执行成功,那么修改的是哪条记录呢?\u003c/p\u003e\n\u003cp\u003e这里通过一番查找,终于定位到了记录。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eAND\u003c/code\u003e分隔符,在mysql语句里优先级最高。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eupdate user_packages set end_date = \u0026#39;2020-06-06\u0026#39; and up_id = 189 limit 1;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e等效为\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eupdate user_packages set end_date = (\u0026#39;2020-06-06\u0026#39; and up_id = 189) limit 1;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e即\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eupdate user_packages set end_date = 0 limit 1;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e因为\u003ccode\u003eend_date\u003c/code\u003e字段为\u003ccode\u003edate\u003c/code\u003e类型,所以写入表后的记录为\u003ccode\u003e0000-00-00\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMySQL \u0026gt; select * from user_packages where end_date = \u0026#39;0000-00-00\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| up_id | start_date | end_date |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e| 185 | 2018-04-01 | 0000-00-00 |\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+-------+------------+------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 rows in set (0.00 sec)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e好在这次只更新了一条记录,否则后果无法想象。\u003c/p\u003e\n\u003cp\u003e切记不要在现网直接操作DB。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e相关资料:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://wing324.github.io/2016/08/25/%E4%B8%80%E4%B8%AA%E6%88%91%E8%AE%A4%E4%B8%BA%E6%98%AFbug%E7%9A%84UPDATE%E8%AF%AD%E5%8F%A5/\"\u003e一个我认为是bug的UPDATE语句\u003c/a\u003e\u003c/p\u003e\n","date_published":"2020-05-19T17:16:53+08:00","tags":["mysql"]},{"title":"如何在北京公积金网站上修改婚姻状况","id":"https://liudon.com/posts/how-to-modify-marital-status/","url":"https://liudon.com/posts/how-to-modify-marital-status/","summary":"\u003cblockquote\u003e\n\u003cp\u003e关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告\u003c/p\u003e\n\u003cp\u003e时间:2020年01月08日\u003c/p\u003e\n\u003cp\u003e来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003e关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告\u003c/p\u003e\n\u003cp\u003e时间:2020年01月08日\u003c/p\u003e\n\u003cp\u003e来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e1月8日,北京公积金中心发文,从1月10日开始可以网上办理公积金提取了。\u003c/p\u003e\n\u003cp\u003e这里单独讲一下外地领证的情况下,如何修改婚姻状况。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e进入提取页面,默认显示为未说明的婚姻状况。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu6904186450706354453.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu15506952337382643592.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"f7HvbKlitaOm1T2.jpg\" width=\"2339\" height=\"276\" alt=\"QQ截图20200117170836.jpg\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e点击婚姻状况,选择已婚。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu1587438054435300824.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu9186879431374056423.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"VYnKduoHTtzDw4f.jpg\" width=\"2338\" height=\"334\" alt=\"QQ截图20200117170917.jpg\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e可以看到婚姻状态相关的输入框都为灰色,不可修改。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e点击婚姻信息修改按钮,会弹出一个民政校验的弹窗,因为我是外地领证,这里查不到信息。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu9138077917932714622.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu13286695534202220428.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"WpV28uBMvjyzPJN.jpg\" width=\"1744\" height=\"586\" alt=\"QQ截图20200117171149.jpg\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e注意图片右下角还是只有一个婚姻信息修改按钮。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e点击弹窗里的确认按钮。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu999288024124128959.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu17392966183303399434.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"kMhnwRzgtrNOFAI.jpg\" width=\"2338\" height=\"342\" alt=\"QQ截图20200117171236.jpg\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e这下婚姻状况相关的输入框都可以填写了。\u003c/p\u003e\n\u003cp\u003e另外图片右下角里多了一个上传结婚证按钮。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e填写完信息后,点击上传结婚证按钮。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu5478875653684974159.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu9672467465638887422.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"fohyvH3iGaTtm1n.jpg\" width=\"1143\" height=\"573\" alt=\"QQ截图20200117171252.jpg\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e按说明上传两张结婚证照片,点击确认即可。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e其余的按\u003ca href=\"http://gjj.beijing.gov.cn/web/bsznx/386727/386730/386732/index.html\"\u003e公积金官网文档\u003c/a\u003e操作,最后提交即可。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e关于提取时间,我是前一天中午申请,第二天下午就到账了,效率还是很棒的。\u003c/p\u003e\n","date_published":"2020-01-17T17:14:32+08:00","tags":["公积金"]},{"title":"PHP7.2编译安装后没有php.ini文件的问题","id":"https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/","url":"https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/","summary":"\u003cp\u003e下载PHP7.2源码,编译安装。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCopyright (c) 1997-2018 The PHP Group\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eZend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装Swoole。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ephpize \u0026amp;\u0026amp; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e./configure \u0026amp;\u0026amp; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emake \u0026amp;\u0026amp; make install\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装完,准备修改\u003ccode\u003ephp.ini\u003c/code\u003e文件,结果没找到。\u003c/p\u003e","content_html":"\u003cp\u003e下载PHP7.2源码,编译安装。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCopyright (c) 1997-2018 The PHP Group\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eZend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装Swoole。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ephpize \u0026amp;\u0026amp; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e./configure \u0026amp;\u0026amp; \\\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emake \u0026amp;\u0026amp; make install\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装完,准备修改\u003ccode\u003ephp.ini\u003c/code\u003e文件,结果没找到。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# ll /usr/local/services/php/etc/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etotal 88\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-r--r-- 1 root root 1364 Nov 26 19:34 pear.conf\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-r--r-- 1 root root 4508 Nov 26 19:34 php-fpm.conf.default\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edrwxr-xr-x 2 root root 4096 Nov 26 19:34 php-fpm.d\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep \u0026#34;Loaded Confi\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLoaded Configuration File =\u0026gt; (none)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这是什么鬼,居然没有\u003ccode\u003ephp.ini\u003c/code\u003e文件。\u003c/p\u003e\n\u003cp\u003e原来PHP源码里提供了两个\u003ccode\u003ephp.ini\u003c/code\u003e文件,你需要按需拷贝到你的PHP的目录下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# ll ../php-7.2.25 | grep ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-rw-r-- 1 root root 71232 Nov 20 23:11 php.ini-development\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-rw-r-- 1 root root 71413 Nov 20 23:11 php.ini-production\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e拷贝后。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep \u0026#34;Loaded Confi\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eLoaded Configuration File =\u0026gt; /usr/local/services/php/etc/php.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_73_135_centos ~/swoole-src-4.4.12]#\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","date_published":"2019-11-26T19:56:18+08:00","tags":["php"]},{"title":"检测网站支持的SSL/TLS协议版本","id":"https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/","url":"https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/","summary":"\u003cblockquote\u003e\n\u003cp\u003eChrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003eChrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e检测是否支持TLSv1\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eopenssl s_client -connect [ip or 域名]:443 -tls1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e检测是否支持TLSv1.1\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eopenssl s_client -connect [ip or 域名]:443 -tls1_1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e检测是否支持TLSv1.2\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eopenssl s_client -connect [ip or 域名]:443 -tls1_2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e参考资料:\u003ca href=\"https://support.plesk.com/hc/en-us/articles/115004991834-How-to-check-what-SSL-TLS-versions-are-available-for-a-website-\"\u003eHow to check what SSL/TLS versions are available for a website?\u003c/a\u003e\u003c/p\u003e\n","date_published":"2019-11-14T14:48:08+08:00","tags":["nginx","ssl"]},{"title":"记一次难忘的手术经历","id":"https://liudon.com/posts/an-unforgettable-surgical-experience/","url":"https://liudon.com/posts/an-unforgettable-surgical-experience/","summary":"有痔青年的手术经历","content_html":"\u003cp\u003e俗话说的好,十人九痔。这九个人里就有我一个。 😂\u003c/p\u003e\n\u003cp\u003e去年因为痔疮去过一趟医院,医生当时建议手术。\u003c/p\u003e\n\u003cp\u003e后来用了点药,没啥症状了,就不放在心上了。\u003c/p\u003e\n\u003cp\u003e结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。\u003c/p\u003e\n\u003cp\u003e第二天赶紧上医院检查,先上开塞露通便,通完舒服多了。\u003c/p\u003e\n\u003cp\u003e医生检查完,让住院手术,这次狠了狠心,手术做掉,一了百了。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e10/15住院\u003c/li\u003e\n\u003cli\u003e10/16手术\u003c/li\u003e\n\u003cli\u003e10/22出院\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e上一次手术是在10年前了,全麻,什么都不知道。\u003c/p\u003e\n\u003cp\u003e这次是局麻,上手术台后,医生往腰上打了一针,很疼。\u003c/p\u003e\n\u003cp\u003e过了10分钟左右,肛门已经使不上劲了,开始手术。\u003c/p\u003e\n\u003cp\u003e过程一直感觉有往里打气,东西插进去。\u003c/p\u003e\n\u003cp\u003e医生说有用镇静,后来越来越困,感觉要晕过去,不过还是撑着没睡。\u003c/p\u003e\n\u003cp\u003e出来后,整个下午下半身都是麻的,没啥精神,事后就记得媳妇坐在我对面。\u003c/p\u003e\n\u003cp\u003e术后6个小时内要小便,不然就得插尿管。\u003c/p\u003e\n\u003cp\u003e第二天开始喝流食,然后就是每天医生给换药。\u003c/p\u003e\n\u003cp\u003e第三天开始大便,伤口那个痛啊。 😭😭😭\u003c/p\u003e\n\u003cp\u003e后来学会在热水里泡着拉,感觉舒服了点。\u003c/p\u003e\n\u003cp\u003e每天都要担心大便,担心不拉,或者担心拉的太多。\u003c/p\u003e\n\u003cp\u003e出院后第二天,早上大便一盆血,以为是大出血,把媳妇叫回来直奔医院。\u003c/p\u003e\n\u003cp\u003e到医院病房,主治医生不在,找了个其他医生给检查,说是有点出血。\u003c/p\u003e\n\u003cp\u003e检查的时候塞了个肛门镜,这玩意实在是痛苦。\u003c/p\u003e\n\u003cp\u003e然后说病房没工具,上门诊处理吧。然后我就拖着身子,走到门诊。\u003c/p\u003e\n\u003cp\u003e主治医生说没啥大事,其实可以不处理,不过你也是来医院了,还是处理一下吧。\u003c/p\u003e\n\u003cp\u003e处理的时候,又塞了个肛门镜,然后往屁眼上打了三针封闭,想死的心都有了。\u003c/p\u003e\n\u003cp\u003e回家后,继续卧床休息,每天换药。\u003c/p\u003e\n\u003cp\u003e朋友们,请一定要好好对你的🌻。\u003c/p\u003e\n","date_published":"2019-10-28T19:03:31+08:00","tags":[]},{"title":"十一假期经历","id":"https://liudon.com/posts/chinese-national-day/","url":"https://liudon.com/posts/chinese-national-day/","summary":"\u003cp\u003e今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。\u003c/p\u003e\n\u003cp\u003e只好请了2天假,提前回家了,给自己也放个假休息一下。\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e回家的几个经历:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。\u003c/p\u003e","content_html":"\u003cp\u003e今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。\u003c/p\u003e\n\u003cp\u003e只好请了2天假,提前回家了,给自己也放个假休息一下。\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e回家的几个经历:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu10598085116215178875.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu11211928567423584928.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"SkRx5uFwQ8Cliyq.jpg\" width=\"4032\" height=\"3024\" alt=\"64F68D7789A8D877A41E6D694ABE5444.png\" title=\"服役了30年的洗衣机\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e村里今年也要通天然气了,各家各户都要拆煤炉,装壁挂炉,希望天然气供应不出问题。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e全家一起去了趟家门口的园博园,希望以后可以走的更远一些。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu15815707449295317792.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu2876570433028549323.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"wfUv5Kb1LGEdNHc.jpg\" width=\"3024\" height=\"4032\" alt=\"F69E19D28B34C163878F2A6E1A43E039.png\" title=\"园博园\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e娃确实是长大了,有了自己的想法,有自己的脾气了。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e背景:晚上开着灯,要睡觉了。\u003c/p\u003e\n\u003cp\u003e我:睡觉吧。\u003c/p\u003e\n\u003cp\u003e娃:爸爸,你说开着灯能睡觉吗?\u003c/p\u003e\n\u003cp\u003e我:不能吧\u0026hellip;(不知道她为啥问这个问题)\u003c/p\u003e\n\u003cp\u003e娃:那你开着灯让我睡觉,我怎么睡呀!\u003c/p\u003e\n\u003cp\u003e我:原来你在这里等着我呢啊\u0026hellip;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003cp\u003e接下来,就努力工作吧。\u003c/p\u003e\n","date_published":"2019-10-08T13:13:36+08:00","tags":[]},{"title":"Swoft 框架运行分析(五) —— ConsoleProcessor模块分析","id":"https://liudon.com/posts/swoft-console-processor-analysis/","url":"https://liudon.com/posts/swoft-console-processor-analysis/","summary":"\u003cblockquote\u003e\n\u003cp\u003e这里以Swoft启动http server为例。\u003c/p\u003e\n\u003cp\u003ephp bin/swoft http:start\u003c/p\u003e\n\u003cp\u003e执行上述命令,启动http server。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e在前面第一篇文章的时候,提到了如何启动http服务。\u003c/p\u003e\n\u003cp\u003e今天我们就来看一下http服务是如何启动的,具体实现就在\u003ccode\u003eConsoleProcess\u003c/code\u003e这个模块。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003e这里以Swoft启动http server为例。\u003c/p\u003e\n\u003cp\u003ephp bin/swoft http:start\u003c/p\u003e\n\u003cp\u003e执行上述命令,启动http server。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e在前面第一篇文章的时候,提到了如何启动http服务。\u003c/p\u003e\n\u003cp\u003e今天我们就来看一下http服务是如何启动的,具体实现就在\u003ccode\u003eConsoleProcess\u003c/code\u003e这个模块。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle console\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeConsole()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Router \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cliRouter\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Register console routes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CommandRegister::register(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Console command route registered (group \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e, command \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e)\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egroupCount(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecount()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Run console application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cliApp\u0026#39;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003erun();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterConsole();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里调用了\u003ccode\u003ebean\u003c/code\u003e方法获取\u003ccode\u003eBean\u003c/code\u003e实例,定义见\u003ccode\u003eswoft-component-2.0.5\\src\\bean\\src\\Helper\\Functions.php\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eif (!function_exists(\u0026#39;bean\u0026#39;)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e /**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Get bean by name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $name Bean name Or alias Or class name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return object|string|mixed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e function bean(string $name)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (BeanFactory::isSingleton(\u0026#39;config\u0026#39;)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return BeanFactory::getBean($name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return sprintf(\u0026#39;${%s}\u0026#39;, $name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里调用了\u003ccode\u003eBeanFactory\u003c/code\u003e的\u003ccode\u003egetBean\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Get object by name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $name Bean name Or alias Or class name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return object|mixed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function getBean(string $name)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return Container::getInstance()-\u0026gt;get($name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e最终调用的是\u003ccode\u003eSwoft\\Bean\\Container\u003c/code\u003e下的\u003ccode\u003eget\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Finds an entry of the container by its identifier \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e returns it\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid Bean name Or alias Or \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e When \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e name will \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e all of instance \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows InvalidArgumentException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function get(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e It is singleton\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esingletonPool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esingletonPool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Prototype by clone\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprototypePool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e clone \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprototypePool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Alias name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealiasId \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid] \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e??\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealiasId) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eget(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealiasId);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Class name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassNames \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid] \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassNames) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e end(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassNames);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eget(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Interface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (interface_exists(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e InterfaceRegister::getInterfaceInjectBean(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eget(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Not defined\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eisset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(sprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;The bean of \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e is not defined\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ObjectDefinition \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Prototype\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esafeNewBean(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取对应的\u003ccode\u003eObjectDefinition\u003c/code\u003e实例,然后调用\u003ccode\u003esafeNewBean\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Secure creation of beans\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $beanName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return object|mixed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function safeNewBean(string $beanName, string $id = \u0026#39;\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e try {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return $this-\u0026gt;newBean($beanName, $id);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } catch (Throwable $e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException($e-\u0026gt;getMessage(), 500, $e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里又调用了\u003ccode\u003enewBean\u003c/code\u003e方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的\u003ccode\u003eBean\u003c/code\u003e类。\u003c/p\u003e\n\u003cp\u003e那\u003ccode\u003ecliRouter\u003c/code\u003e对应的类是说明呢?这个定义在\u003ccode\u003eswoft-component-2.0.5\\src\\console\\src\\AutoLoader.php\u003c/code\u003e里。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * {@inheritDoc}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function beans(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;cliApp\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;class\u0026#39; =\u0026gt; Application::class,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;version\u0026#39; =\u0026gt; \u0026#39;2.0.0\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;cliRouter\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;class\u0026#39; =\u0026gt; Router::class,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;cliDispatcher\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;class\u0026#39; =\u0026gt; ConsoleDispatcher::class,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e所以\u003ccode\u003e$router = bean('cliRouter')\u003c/code\u003e,返回的是一个\u003ccode\u003eSwoft\\Console\\Router\\Router\u003c/code\u003e类。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eConsoleProcessor\u003c/code\u003e类,接着看代码。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCommandRegister::register($router);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e调用了\u003ccode\u003eCommandRegister\u003c/code\u003e类的\u003ccode\u003eregister\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param Router $router\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function register(Router $router): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $maxLen = 12;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groups = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $docOpts = [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;allow\u0026#39; =\u0026gt; [\u0026#39;example\u0026#39;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $defInfo = [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;example\u0026#39; =\u0026gt; \u0026#39;\u0026#39;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;description\u0026#39; =\u0026gt; \u0026#39;No description message\u0026#39;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (self::$commands as $class =\u0026gt; $mapping) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $names = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $group = $mapping[\u0026#39;group\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Set ID aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;setIdAliases($mapping[\u0026#39;idAliases\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Set group name aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;setGroupAliases($group, $mapping[\u0026#39;aliases\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $refInfo = Swoft::getReflection($class);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $mhdInfo = $refInfo[\u0026#39;methods\u0026#39;] ?? [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $grpOpts = $mapping[\u0026#39;options\u0026#39;] ?? [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($mapping[\u0026#39;commands\u0026#39;] as $method =\u0026gt; $route) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // $method = $route[\u0026#39;method\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $cmdDesc = $route[\u0026#39;desc\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $command = $route[\u0026#39;command\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $idLen = strlen($group . $command);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($idLen \u0026gt; $maxLen) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $maxLen = $idLen;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $cmdExam = \u0026#39;\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($mhdInfo[$method][\u0026#39;comments\u0026#39;])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $tagInfo = DocBlock::getTags($mhdInfo[$method][\u0026#39;comments\u0026#39;], $docOpts, $defInfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo[\u0026#39;description\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $cmdExam = $tagInfo[\u0026#39;example\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;group\u0026#39;] = $group;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;desc\u0026#39;] = ucfirst($cmdDesc);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;example\u0026#39;] = $cmdExam;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;options\u0026#39;] = self::mergeOptions($grpOpts, $route[\u0026#39;options\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Append group option\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;enabled\u0026#39;] = $mapping[\u0026#39;enabled\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;coroutine\u0026#39;] = $mapping[\u0026#39;coroutine\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;map($group, $command, [$class, $method], $route);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $names[] = $command;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groupExam = \u0026#39;\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groupDesc = $mapping[\u0026#39;desc\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($refInfo[\u0026#39;comments\u0026#39;])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $tagInfo = DocBlock::getTags($refInfo[\u0026#39;comments\u0026#39;], $docOpts, $defInfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groupDesc = $groupDesc ?: Str::firstLine($tagInfo[\u0026#39;description\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groupExam = $tagInfo[\u0026#39;example\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $groups[$group] = [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;names\u0026#39; =\u0026gt; $names,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;desc\u0026#39; =\u0026gt; ucfirst($groupDesc),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;class\u0026#39; =\u0026gt; $class,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;alias\u0026#39; =\u0026gt; $mapping[\u0026#39;alias\u0026#39;],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;aliases\u0026#39; =\u0026gt; $mapping[\u0026#39;aliases\u0026#39;],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;example\u0026#39; =\u0026gt; $groupExam,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;setGroups($groups);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // +1 because router-\u0026gt;delimiter\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;setKeyWidth($maxLen + 1);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // clear data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$commands = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里遍历了类属性\u003ccode\u003e$commands\u003c/code\u003e注册路由。\u003c/p\u003e\n\u003cp\u003e那么\u003ccode\u003e$commands\u003c/code\u003e这个属性是哪里来的呢?\u003c/p\u003e\n\u003cp\u003e既然开头我们说的是http服务是怎么启动的,这里我们就以\u003ccode\u003ehttp-server\u003c/code\u003e来举例,找到\u003ccode\u003eswoft-component-2.0.5\\src\\http-server\\src\\Command\\HttpServerCommand.php\u003c/code\u003e文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Http\\Server\\Command;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse ReflectionException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Exception\\ContainerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\Annotation\\Mapping\\Command;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\Annotation\\Mapping\\CommandMapping;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\Annotation\\Mapping\\CommandOption;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\Helper\\Show;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\HttpServer;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Server\\Command\\BaseServerCommand;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Server\\Exception\\ServerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function input;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function output;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Provide some commands to manage the swoft HTTP server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommand(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http\u0026#34;\u003c/span\u003e, alias\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;httpsrv\u0026#34;\u003c/span\u003e, coroutine\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003efalse)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eexample\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCmd}:start Start the http server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCmd}:stop Stop the http server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e HttpServerCommand \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e BaseServerCommand\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Start the http server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandMapping(usage\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{fullCommand} [-d|--daemon]\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandOption(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;daemon\u0026#34;\u003c/span\u003e, short\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d\u0026#34;\u003c/span\u003e, desc\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Run server on the background\u0026#34;\u003c/span\u003e, type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;bool\u0026#34;\u003c/span\u003e, default\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;false\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eexample\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCommand}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCommand} \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function start(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecreateServer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Check \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e it has started\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisRunning()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emasterPid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPid();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewriteln(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026lt;error\u0026gt;The HTTP server have been running!(PID: {$masterPid})\u0026lt;/error\u0026gt;\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Startup settings\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003econfigStartOption(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esettings \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetSetting();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Setting\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eworkerNum \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esettings[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;worker_num\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Server startup parameters\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emainHost \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetHost();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emainPort \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPort();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emodeName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetModeName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etypeName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetTypeName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Http\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epanel \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;HTTP\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;listen\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emainHost \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;:\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emainPort,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;type\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etypeName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;mode\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emodeName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;worker\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eworkerNum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Port Listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epanel \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eappendPortsToPanel(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epanel);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Show::panel(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epanel);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewriteln(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;success\u0026gt;HTTP server start success !\u0026lt;/success\u0026gt;\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Start the server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003estart();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Reload worker processes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandMapping(usage\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{fullCommand} [-t]\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandOption(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;t\u0026#34;\u003c/span\u003e, desc\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Only to reload task processes, default to reload worker and task\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function reload(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecreateServer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e input()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetScript();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Check \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e it has started\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisRunning()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewriteln(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;error\u0026gt;The HTTP server is not running! cannot reload\u0026lt;/error\u0026gt;\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewritef(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;info\u0026gt;Server \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e is reloading\u0026lt;/info\u0026gt;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereloadTask \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e input()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehasOpt(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;t\u0026#39;\u003c/span\u003e)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Show::notice(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Will only reload task worker\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ereload(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereloadTask)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Show::error(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;The swoole server worker process reload fail!\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewritef(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;success\u0026gt;HTTP server \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e reload success\u0026lt;/success\u0026gt;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Stop the currently running server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandMapping()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function stop(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecreateServer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Check \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e it has started\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisRunning()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewriteln(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;error\u0026gt;The HTTP server is not running! cannot stop.\u0026lt;/error\u0026gt;\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Do stopping\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003estop();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Restart the http server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandMapping(usage\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{fullCommand} [-d|--daemon]\u0026#34;\u003c/span\u003e,)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eCommandOption(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;daemon\u0026#34;\u003c/span\u003e, short\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;d\u0026#34;\u003c/span\u003e, desc\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Run server on the background\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eexample\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCommand}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e {fullCommand} \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function restart(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecreateServer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Check \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e it has started\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisRunning()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esuccess \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003estop();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003esuccess) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eerror(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Stop the old server failed!\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewritef(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026lt;success\u0026gt;Server HTTP restart success !\u0026lt;/success\u0026gt;\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003estartWithDaemonize();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e HttpServer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private function createServer(): HttpServer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e input()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetScript();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetFullCommand();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e HttpServer \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpServer\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetScriptFile(Swoft::app()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPath(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetFullCommand(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecommand);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过\u003ca href=\"https://www.swoft.org/docs/2.x/zh-CN/annotation/usage.html\"\u003eSwoft文档\u003c/a\u003e,我们可以看到这里分别使用了类注解和方法注解。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@Command(\u0026#34;http\u0026#34;, alias=\u0026#34;httpsrv\u0026#34;, coroutine=false)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@CommandMapping(usage=\u0026#34;{fullCommand} [-d|--daemon]\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@CommandOption(\u0026#34;daemon\u0026#34;, short=\u0026#34;d\u0026#34;, desc=\u0026#34;Run server on the background\u0026#34;, type=\u0026#34;bool\u0026#34;, default=\u0026#34;false\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003eSwoft\\Console\\Annotation\\Mapping\\CommandMapping\u003c/code\u003e这个注解为例,对应的注解解析类为\u003ccode\u003eSwoft\\Console\\Annotation\\Parser\\CommandMappingParser\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Console\\Annotation\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Mapping\\AnnotationParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Parser\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Exception\\AnnotationException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\Annotation\\Mapping\\CommandMapping;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Console\\CommandRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class CommandMappingParser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAnnotationParser(CommandMapping::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e CommandMappingParser \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e Parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Parse object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype Class \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e Method \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e Property\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam CommandMapping \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation Annotation object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Return empty array is nothing to \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e When \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e type \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealias, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esize] is to inject bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e When property type \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epropertyValue, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eisRef] is to reference value\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function parse(\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype \u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003e self::TYPE_METHOD) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new AnnotationException(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;`@CommandMapping` must be defined on class method!\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003emethodName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e add route info \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e controller action\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CommandRegister::addRoute(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod, [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;command\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName() \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e: \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;method\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;alias\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAlias(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;aliases\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAliases(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;desc\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDesc(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;usage\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetUsage(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;example\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetExample(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e看到这里,你应该可以猜到\u003ccode\u003eCommandRegister\u003c/code\u003e类的\u003ccode\u003e$commands\u003c/code\u003e是怎么来的了吧。\u003c/p\u003e\n\u003cp\u003e我们看下\u003ccode\u003eCommandRegister\u003c/code\u003e类的\u003ccode\u003eaddRoute\u003c/code\u003e方法,验证下想法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $class\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $method\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $route\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function addRoute(string $class, string $method, array $route): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::checkClass($class);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // init some keys\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;options\u0026#39;] = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route[\u0026#39;arguments\u0026#39;] = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // save\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$commands[$class][\u0026#39;commands\u0026#39;][$method] = $route;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ebingo,跟我们猜想的一模一样,这下我们也知道\u003ccode\u003eCommandMapping\u003c/code\u003e这个注解是用来注册终端的路由信息。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eConsoleProcessor\u003c/code\u003e类,接着看代码。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCLog::info(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;Console command route registered (group %d, command %d)\u0026#39;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;groupCount(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $router-\u0026gt;count()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e打印日志。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Run console application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebean(\u0026#39;cliApp\u0026#39;)-\u0026gt;run();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e感觉到了重头戏。\u003c/p\u003e\n\u003cp\u003e根据前面的代码,我们知道\u003ccode\u003ecliApp\u003c/code\u003e这个\u003ccode\u003eBean\u003c/code\u003e实例对应的类是\u003ccode\u003eSwoft\\Console\\Application\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function run(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e try {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ConsoleEvent::RUN_BEFORE, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Prepare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprepare();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Get input command\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einput\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetCommand();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003einputCommand) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003efilterSpecialOption();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edoRun(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCommand);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ConsoleEvent::RUN_AFTER, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCommand);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } catch (Throwable \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ConsoleErrorDispatcher \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eerrDispatcher \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eerrDispatcher \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeanFactory::getSingleton(ConsoleErrorDispatcher::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Handle request error\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eerrDispatcher\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003erun(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ee);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过\u003ccode\u003eSwoft::trigger\u003c/code\u003e,注册了\u003ccode\u003eConsoleEvent::RUN_BEFORE\u003c/code\u003e和\u003ccode\u003eConsoleEvent::RUN_AFTER\u003c/code\u003e两个事件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function prepare(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einput \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \\input();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eoutput \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \\output();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e load builtin comments vars\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetCommentsVars(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ecommentsVars());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eprepare\u003c/code\u003e比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$inputCommand = $this-\u0026gt;input-\u0026gt;getCommand();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eif (!$inputCommand) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;filterSpecialOption();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e} else {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;doRun($inputCommand);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取终端命令行下的输入,如果有输入执行\u003ccode\u003edoRun\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCmd\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows Throwable\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function doRun(string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCmd): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eoutput \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eoutput;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Router \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Swoft::getBean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cliRouter\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresult \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ematch(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCmd);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Command \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e found\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresult[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e Router::NOT_FOUND) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003enames \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAllNames();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eoutput\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eliteError(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;The entered command \u0026#39;{$inputCmd}\u0026#39; is not exists!\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e find similar command names by similar_text()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esimilar \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Arr::findSimilar(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einputCmd, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003enames)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eoutput\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ewritef(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eMaybe what you mean is:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u0026lt;info\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026lt;/info\u0026gt;\u0026#34;\u003c/span\u003e, implode(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;, \u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esimilar));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eshowApplicationHelp(false);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresult[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Only input a group name, display help \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e the group\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresult[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e Router::ONLY_GROUP) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eshowGroupHelp(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;group\u0026#39;\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Display help \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e a command\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einput\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetSameOpt([\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;h\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;help\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eshowCommandHelp(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse default options \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e arguments\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebindCommandFlags(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einput\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetCommandId(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cmdId\u0026#39;\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Call command handler\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ConsoleDispatcher \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Swoft::getSingleton(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cliDispatcher\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edispatch(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$router = Swoft::getBean(\u0026#39;cliRouter\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$result = $router-\u0026gt;match($inputCmd);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取\u003ccode\u003ecliRouter\u003c/code\u003e实例,根据输入匹配路由操作类。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Match route by input command\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $params [$route]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * status, info(array)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function match(...$params): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $delimiter = $this-\u0026gt;delimiter;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $inputCmd = trim($params[0], \u0026#34;$delimiter \u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $noSepChar = strpos($inputCmd, $delimiter) === false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // If use command ID alias\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($noSepChar \u0026amp;\u0026amp; isset($this-\u0026gt;idAliases[$inputCmd])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $inputCmd = $this-\u0026gt;idAliases[$inputCmd];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Must re-check\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $noSepChar = strpos($inputCmd, $delimiter) === false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($noSepChar \u0026amp;\u0026amp; in_array($inputCmd, $this-\u0026gt;defaultCommands, true)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $group = $this-\u0026gt;defaultGroup;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $command = $this-\u0026gt;resolveCommandAlias($inputCmd);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Only a group name\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif ($noSepChar) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $group = $this-\u0026gt;resolveGroupAlias($inputCmd);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (isset($this-\u0026gt;groups[$group])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [self::ONLY_GROUP, [\u0026#39;group\u0026#39; =\u0026gt; $group]];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [self::NOT_FOUND];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } else {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $nameList = explode($delimiter, $inputCmd, 2);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (count($nameList) === 2) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [$group, $command] = $nameList;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // resolve command alias\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $command = $this-\u0026gt;resolveCommandAlias($command);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } else {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $command = \u0026#39;\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // $command = $this-\u0026gt;defaultCommand;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $group = $nameList[0];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $group = $this-\u0026gt;resolveGroupAlias($group);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // build command ID\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $commandID = $this-\u0026gt;buildCommandID($group, $command);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (isset($this-\u0026gt;routes[$commandID])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $info = $this-\u0026gt;routes[$commandID];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // append some info\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $info[\u0026#39;cmdId\u0026#39;] = $commandID;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [self::FOUND, $info];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($group \u0026amp;\u0026amp; isset($this-\u0026gt;groups[$group])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [self::ONLY_GROUP, [\u0026#39;group\u0026#39; =\u0026gt; $group]];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [self::NOT_FOUND];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里会返回匹配后的路由信息。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003edoRun\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Command not found\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eif ($result[0] === Router::NOT_FOUND) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $names = $router-\u0026gt;getAllNames();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $output-\u0026gt;liteError(\u0026#34;The entered command \u0026#39;{$inputCmd}\u0026#39; is not exists!\u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // find similar command names by similar_text()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($similar = Arr::findSimilar($inputCmd, $names)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $output-\u0026gt;writef(\u0026#34;\\nMaybe what you mean is:\\n \u0026lt;info\u0026gt;%s\u0026lt;/info\u0026gt;\u0026#34;, implode(\u0026#39;, \u0026#39;, $similar));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } else {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;showApplicationHelp(false);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$info = $result[1];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Only input a group name, display help for the group\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eif ($result[0] === Router::ONLY_GROUP) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;showGroupHelp($info[\u0026#39;group\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Display help for a command\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eif ($this-\u0026gt;input-\u0026gt;getSameOpt([\u0026#39;h\u0026#39;, \u0026#39;help\u0026#39;])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;showCommandHelp($info);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e根据返回的路由信息进行不同的处理。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Parse default options and arguments\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;bindCommandFlags($info);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;input-\u0026gt;setCommandId($info[\u0026#39;cmdId\u0026#39;]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSwoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e绑定默认参数,注册\u003ccode\u003eConsoleEvent::DISPATCH_BEFORE\u003c/code\u003e事件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Call command handler\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ConsoleDispatcher \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Swoft::getSingleton(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;cliDispatcher\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edispatcher\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edispatch(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003einfo);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取\u003ccode\u003ecliDispatcher\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例,对应\u003ccode\u003eSwoft\\Console\\ConsoleDispatcher\u003c/code\u003e类,调用\u003ccode\u003edispatch\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $params\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws Throwable\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function dispatch(...$params): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $route = $params[0];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Handler info\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [$className, $method] = $route[\u0026#39;handler\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Bind method params\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $params = $this-\u0026gt;getBindParams($className, $method);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $object = Swoft::getSingleton($className);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Blocking running\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!$route[\u0026#39;coroutine\u0026#39;]) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;before(get_parent_class($object), $method);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PhpHelper::call([$object, $method], ...$params);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;after($method);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Hook php io function\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Runtime::enableCoroutine();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // If in unit test env, has been in coroutine.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\\defined(\u0026#39;PHPUNIT_COMPOSER_INSTALL\u0026#39;)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;executeByCo($object, $method, $params);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Coroutine running\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e srun(function () use ($object, $method, $params) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;executeByCo($object, $method, $params);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e });\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取路由对应的类和方法,通过\u003ccode\u003eSwoft::getSingleton($className);\u003c/code\u003e实例化对象。\u003c/p\u003e\n\u003cp\u003e如果未开启协程,则用\u003ccode\u003ePhpHelper::call([$object, $method], ...$params);\u003c/code\u003e调用对应的方法。\u003c/p\u003e\n\u003cp\u003e开启协程的话,使用\u003ccode\u003e$this-\u0026gt;executeByCo($object, $method, $params);\u003c/code\u003e调用对应的方法。\u003c/p\u003e\n\u003cp\u003e我们前面启动命令是\u003ccode\u003ephp bin/swoft http:start\u003c/code\u003e,这里对应的类就是\u003ccode\u003eSwoft\\Http\\Server\\Command\\HttpServerCommand\u003c/code\u003e,方法就是\u003ccode\u003estart\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Start the http server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @CommandMapping(usage=\u0026#34;{fullCommand} [-d|--daemon]\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @CommandOption(\u0026#34;daemon\u0026#34;, short=\u0026#34;d\u0026#34;, desc=\u0026#34;Run server on the background\u0026#34;, type=\u0026#34;bool\u0026#34;, default=\u0026#34;false\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @example\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * {fullCommand}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * {fullCommand} -d\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function start(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $server = $this-\u0026gt;createServer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Check if it has started\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($server-\u0026gt;isRunning()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $masterPid = $server-\u0026gt;getPid();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()-\u0026gt;writeln(\u0026#34;\u0026lt;error\u0026gt;The HTTP server have been running!(PID: {$masterPid})\u0026lt;/error\u0026gt;\u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Startup settings\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;configStartOption($server);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $settings = $server-\u0026gt;getSetting();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Setting\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $workerNum = $settings[\u0026#39;worker_num\u0026#39;];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Server startup parameters\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $mainHost = $server-\u0026gt;getHost();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $mainPort = $server-\u0026gt;getPort();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $modeName = $server-\u0026gt;getModeName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $typeName = $server-\u0026gt;getTypeName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Http\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $panel = [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;HTTP\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;listen\u0026#39; =\u0026gt; $mainHost . \u0026#39;:\u0026#39; . $mainPort,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;type\u0026#39; =\u0026gt; $typeName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;mode\u0026#39; =\u0026gt; $modeName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;worker\u0026#39; =\u0026gt; $workerNum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Port Listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $panel = $this-\u0026gt;appendPortsToPanel($server, $panel);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Show::panel($panel);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e output()-\u0026gt;writeln(\u0026#39;\u0026lt;success\u0026gt;HTTP server start success !\u0026lt;/success\u0026gt;\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Start the server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $server-\u0026gt;start();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里先调用了\u003ccode\u003ecreateServer\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e HttpServer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function createServer(): HttpServer\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e input()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetScript();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetFullCommand();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e HttpServer \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpServer\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetScriptFile(Swoft::app()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPath(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escript));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetFullCommand(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecommand);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eserver;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取\u003ccode\u003ehttpServer\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例。\u003c/p\u003e\n\u003cp\u003e框架定义在\u003ccode\u003eswoft-component-2.0.5\\src\\http-server\\src\\AutoLoader.php\u003c/code\u003e,这里声明了\u003ccode\u003eonRequest\u003c/code\u003e回调事件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026#39;httpServer\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;on\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e SwooleEvent::REQUEST =\u0026gt; bean(RequestListener::class)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e业务定义在\u003ccode\u003eswoft-2.0.5\\app\\bean.php\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026#39;httpServer\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;class\u0026#39; =\u0026gt; HttpServer::class,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;port\u0026#39; =\u0026gt; 18306,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;listener\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;rpc\u0026#39; =\u0026gt; bean(\u0026#39;rpcServer\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;process\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// \u0026#39;monitor\u0026#39; =\u0026gt; bean(MonitorProcess::class)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// \u0026#39;crontab\u0026#39; =\u0026gt; bean(CrontabProcess::class)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;on\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// SwooleEvent::TASK =\u0026gt; bean(SyncTaskListener::class), // Enable sync task\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e SwooleEvent::TASK =\u0026gt; bean(TaskListener::class), // Enable task must task and finish event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e SwooleEvent::FINISH =\u0026gt; bean(FinishListener::class)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e /* @see HttpServer::$setting */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;setting\u0026#39; =\u0026gt; [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;task_worker_num\u0026#39; =\u0026gt; 12,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;task_enable_coroutine\u0026#39; =\u0026gt; true\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003ecreateServer\u003c/code\u003e返回的是一个\u003ccode\u003eSwoft\\Http\\Server\\HttpServer\u003c/code\u003e实例。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eHttpServerCommand\u003c/code\u003e类的\u003ccode\u003estart\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Start the server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$server-\u0026gt;start();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e调用\u003ccode\u003eSwoft\\Http\\Server\\HttpServer\u003c/code\u003e类的\u003ccode\u003estart\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Start server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function start(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;swooleServer = new \\Swoole\\Http\\Server($this-\u0026gt;host, $this-\u0026gt;port, $this-\u0026gt;mode, $this-\u0026gt;type);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;startSwoole();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e声明\u003ccode\u003eSwoole\\Http\\Server\u003c/code\u003e对象,调用\u003ccode\u003estartSwoole\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eSwoft\\Http\\Server\\HttpServer\u003c/code\u003e类继承自\u003ccode\u003eSwoft\\Server\\Server\u003c/code\u003e类,\u003ccode\u003estartSwoole\u003c/code\u003e方法定义在这个类。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Bind swoole event and start swoole server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws Swoft\\Bean\\Exception\\ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function startSwoole(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!$this-\u0026gt;swooleServer) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new ServerException(\u0026#39;You must to new server before start swoole!\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Always enable coroutine hook on server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u0026#39;Swoole\\Runtime::enableCoroutine\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Runtime::enableCoroutine();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Set settings\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;swooleServer-\u0026gt;set($this-\u0026gt;setting);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Update setting property\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // $this-\u0026gt;setSetting($this-\u0026gt;swooleServer-\u0026gt;setting);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Before Add event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Register events\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $defaultEvents = $this-\u0026gt;defaultEvents();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $swooleEvents = array_merge($defaultEvents, $this-\u0026gt;on);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Add events\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;addEvent($this-\u0026gt;swooleServer, $swooleEvents, $defaultEvents);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e //After add event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Before listener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Add port listener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;addListener();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Before bind process\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Add Process\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // After bind process\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Trigger event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Storage server instance\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$server = $this;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Start swoole server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;swooleServer-\u0026gt;start();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;swooleServer-\u0026gt;set($this-\u0026gt;setting);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e设置\u003ccode\u003eSwoole\u003c/code\u003e运行配置。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Register events\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$defaultEvents = $this-\u0026gt;defaultEvents();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$swooleEvents = array_merge($defaultEvents, $this-\u0026gt;on);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Add events\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;addEvent($this-\u0026gt;swooleServer, $swooleEvents, $defaultEvents);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e添加\u003ccode\u003eSwoole\u003c/code\u003e回调事件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Add port listener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;addListener();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e监听端口。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Start swoole server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$this-\u0026gt;swooleServer-\u0026gt;start();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e启动\u003ccode\u003eSwoole\\Http\\Server\u003c/code\u003e服务。\u003c/p\u003e\n\u003cp\u003e现在服务已经启动了,那\u003ccode\u003ehttp请求\u003c/code\u003e是怎么被处理的呢?\u003c/p\u003e\n\u003cp\u003e这个我们下一篇再继续讲。\u003c/p\u003e\n","date_published":"2019-09-26T13:14:23+08:00","tags":["Swoft"]},{"title":"Swoft 框架运行分析(四) —— EventProcessor模块分析","id":"https://liudon.com/posts/swoft-event-processor-analysis/","url":"https://liudon.com/posts/swoft-event-processor-analysis/","summary":"\u003cp\u003e今天我们来看一下\u003ccode\u003eEventProcessor\u003c/code\u003e的实现。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle event register\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeEvent()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::warning(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Stop event processor by beforeEvent return false\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e EventManager \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;eventManager\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount1, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount2] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ListenerRegister::register(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Event manager initialized(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e listener, \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e subscriber)\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount1, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount2);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Trigger a app init event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterEvent();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取\u003ccode\u003eeventManager\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例,对应为\u003ccode\u003eSwoft\\Event\\Manager\\EventManager\u003c/code\u003e类。\u003c/p\u003e","content_html":"\u003cp\u003e今天我们来看一下\u003ccode\u003eEventProcessor\u003c/code\u003e的实现。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle event register\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeEvent()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::warning(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Stop event processor by beforeEvent return false\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e EventManager \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;eventManager\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount1, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount2] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ListenerRegister::register(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eeventManager);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Event manager initialized(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e listener, \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e subscriber)\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount1, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ecount2);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Trigger a app init event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterEvent();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e获取\u003ccode\u003eeventManager\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例,对应为\u003ccode\u003eSwoft\\Event\\Manager\\EventManager\u003c/code\u003e类。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[$count1, $count2] = ListenerRegister::register($eventManager);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e调用\u003ccode\u003eListenerRegister\u003c/code\u003e类的\u003ccode\u003eregister\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param EventManager $em\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function register(EventManager $em): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (self::$listeners as $className =\u0026gt; $eventInfo) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener = Swoft::getSingleton($className);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!$listener instanceof EventHandlerInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new RuntimeException(\u0026#34;The event listener class \u0026#39;{$className}\u0026#39; must be instanceof EventHandlerInterface\u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $em-\u0026gt;addListener($listener, $eventInfo);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (self::$subscribers as $className) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $subscriber = Swoft::getSingleton($className);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!$subscriber instanceof EventSubscriberInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new RuntimeException(\u0026#34;The event subscriber class \u0026#39;{$className}\u0026#39; must be instanceof EventSubscriberInterface\u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $em-\u0026gt;addSubscriber($subscriber);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $count1 = count(self::$listeners);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $count2 = count(self::$subscribers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Clear data\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$listeners = self::$subscribers = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [$count1, $count2];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e遍历\u003ccode\u003eListenerRegister\u003c/code\u003e类下的\u003ccode\u003e$listeners\u003c/code\u003e和\u003ccode\u003e$subscribers\u003c/code\u003e属性,绑定事件到\u003ccode\u003eeventManager\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例上。\u003c/p\u003e\n\u003cp\u003e这里的\u003ccode\u003e$listeners\u003c/code\u003e和\u003ccode\u003e$subscribers\u003c/code\u003e是从哪里来的呢?\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003ehttp-server\u003c/code\u003e为例。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eswoft-component-2.0.5\\src\\http-server\\src\\Listener\u003c/code\u003e目录下,存在下面三个文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAfterRequestListener.php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAppInitCompleteListener.php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeforeRequestListener.php\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里我们以\u003ccode\u003eAppInitCompleteListener.php\u003c/code\u003e为例。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Http\\Server\\Listener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse ReflectionException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Exception\\ContainerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\Annotation\\Mapping\\Listener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\EventHandlerInterface;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\EventInterface;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Exception\\HttpServerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Middleware\\MiddlewareRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Router\\Router;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Router\\RouteRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\SwoftEvent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class AppInitCompleteListener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eListener(SwoftEvent::APP_INIT_COMPLETE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e AppInitCompleteListener implements EventHandlerInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam EventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows HttpServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function handle(EventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Router \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter Register HTTP routes \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpRouter\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e RouteRegister::registerRoutes(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Register middleware\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e MiddlewareRegister::register();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看到这里通过\u003ccode\u003e@Listener(SwoftEvent::APP_INIT_COMPLETE)\u003c/code\u003e,使用了\u003ccode\u003eSwoft\\Event\\Annotation\\Mapping\\Listener\u003c/code\u003e类注解,对应的注解解析类为\u003ccode\u003eSwoft\\Event\\Annotation\\Parser\\ListenerParser\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Event\\Annotation\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Doctrine\\Common\\Annotations\\AnnotationException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Mapping\\AnnotationParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Parser\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Annotation\\Mapping\\Bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\Annotation\\Mapping\\Listener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\ListenerRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class ListenerParser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAnnotationParser(Listener::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e ListenerParser \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e Parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam Listener \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function parse(\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype \u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003e self::TYPE_CLASS) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new AnnotationException(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;`@Listener` must be defined on class!\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e collect listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ListenerRegister::addListener(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassName, [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e event name \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e listener priority\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetEvent() \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotation\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPriority()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassName, Bean::SINGLETON, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-zed\" data-lang=\"zed\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param string $className\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param array $definition [event name =\u0026gt; listener priority]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function addListener(string \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName, array \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [])\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Collect listeners\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003elisteners[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看到这里通过\u003ccode\u003eListenerRegister::addListener\u003c/code\u003e方法,往\u003ccode\u003eListenerRegister\u003c/code\u003e上注册了\u003ccode\u003e$listeners\u003c/code\u003e属性。\u003c/p\u003e\n\u003cp\u003e属性\u003ccode\u003e$listeners\u003c/code\u003e和\u003ccode\u003e$subscribers\u003c/code\u003e的值,都是通过注解解析得来。\u003c/p\u003e\n\u003cp\u003e这里我们回到\u003ccode\u003eEventProcessor\u003c/code\u003e类的\u003ccode\u003ehandle\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Trigger a app init event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSwoft::trigger(SwoftEvent::APP_INIT_COMPLETE);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003etrigger的方法定义如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Trigger an swoft application event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string|EventInterface $event eg: \u0026#39;app.start\u0026#39; \u0026#39;app.stop\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param null|mixed $target\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $params\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return EventInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function trigger($event, $target = null, ...$params): EventInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e /** @see EventManager::trigger() */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return BeanFactory::getSingleton(\u0026#39;eventManager\u0026#39;)-\u0026gt;trigger($event, $target, $params);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里调用了\u003ccode\u003eeventManager\u003c/code\u003e这个\u003ccode\u003eBean\u003c/code\u003e实例的\u003ccode\u003etrigger\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Trigger an event\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e Can accept an EventInterface \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e will create one \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e passed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003eEventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;app.start\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;app.stop\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam mixed\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003estring \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget It is object \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e string\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003emixed \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eargs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e EventInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows InvalidArgumentException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function trigger(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e null, array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eargs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []): EventInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eisString \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e is_string(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e trim(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent instanceof EventInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e trim(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Invalid event params for trigger event handler\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eshouldCall \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Have matched listener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003elistenedEvents[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eshouldCall[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Like \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;app.db.query\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e prefix: \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;app.db\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epos \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e strrpos(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.\u0026#39;\u003c/span\u003e)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprefix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e substr(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epos);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Have a wildcards listener\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e eg \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;app.db.*\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ewildcardEvent \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprefix \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.*\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003elistenedEvents[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ewildcardEvent])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eshouldCall[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ewildcardEvent] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e substr(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epos \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Not found listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eshouldCall) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eisString \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebasicEvent : \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e EventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eisString) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eevents[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename] \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e??\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebasicEvent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Initial value\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetName(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetParams(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eargs);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetTarget(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003estopPropagation(false);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Notify event listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eshouldCall as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003etriggerListeners(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003elisteners[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename], \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emethod);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisPropagationStopped()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Have global wildcards \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e listener\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003elistenedEvents[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003etriggerListeners(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003elisteners[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e如果存在对应的事件,调用\u003ccode\u003etriggerListeners\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array|ListenerQueue $listeners\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param EventInterface $event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $method\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function triggerListeners($listeners, EventInterface $event, string $method = \u0026#39;\u0026#39;): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // $handled = false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $name = $event-\u0026gt;getName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $callable = false === strpos($name, \u0026#39;.\u0026#39;);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // 循环调用监听器,处理事件\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($listeners as $listener) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($event-\u0026gt;isPropagationStopped()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e break;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (is_object($listener)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($listener instanceof EventHandlerInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener-\u0026gt;handle($event);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif ($method \u0026amp;\u0026amp; method_exists($listener, $method)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener-\u0026gt;$method($event);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif ($callable \u0026amp;\u0026amp; method_exists($listener, $name)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener-\u0026gt;$name($event);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif (method_exists($listener, \u0026#39;__invoke\u0026#39;)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener($event);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif (is_callable($listener)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $listener($event);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e遍历事件回调,执行对应方法。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eEventProcessor\u003c/code\u003e类的\u003ccode\u003ehandle\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Trigger a app init event\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSwoft::trigger(SwoftEvent::APP_INIT_COMPLETE);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里的事件为\u003ccode\u003eSwoftEvent::APP_INIT_COMPLETE\u003c/code\u003e,所以这里会执行这个事件下的所有回调。\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003eSwoft\\Http\\Server\\Listener\\AppInitCompleteListener\u003c/code\u003e为例。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Http\\Server\\Listener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse ReflectionException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Exception\\ContainerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\Annotation\\Mapping\\Listener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\EventHandlerInterface;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Event\\EventInterface;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Exception\\HttpServerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Middleware\\MiddlewareRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Router\\Router;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Router\\RouteRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\SwoftEvent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class AppInitCompleteListener\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eListener(SwoftEvent::APP_INIT_COMPLETE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e AppInitCompleteListener implements EventHandlerInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam EventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows HttpServerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function handle(EventInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eevent): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Router \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter Register HTTP routes \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpRouter\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e RouteRegister::registerRoutes(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003erouter);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Register middleware\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e MiddlewareRegister::register();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里使用了\u003ccode\u003eSwoft\\Event\\Annotation\\Mapping\\Listener\u003c/code\u003e注解,对应的事件为\u003ccode\u003eSwoftEvent::APP_INIT_COMPLETE\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e按照上面的分析,这里会调用到\u003ccode\u003eAppInitCompleteListener\u003c/code\u003e的\u003ccode\u003ehandle\u003c/code\u003e方法,获取\u003ccode\u003ehttpRouter\u003c/code\u003e的\u003ccode\u003eBean\u003c/code\u003e实例,注册\u003ccode\u003ehttp服务\u003c/code\u003e的路由信息和中间件。\u003c/p\u003e\n\u003cp\u003e到这里,我们大概清楚了\u003ccode\u003eEventProcessor\u003c/code\u003e这个模块的作用,注册了所有事件的回调。\u003c/p\u003e\n","date_published":"2019-09-26T13:02:18+08:00","tags":["Swoft"]},{"title":"一个git submodule update引发的问题","id":"https://liudon.com/posts/a-issues-of-git-submodule-update/","url":"https://liudon.com/posts/a-issues-of-git-submodule-update/","summary":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e1月份的时候,用\u003ccode\u003ehugo\u003c/code\u003e搭了这套博客系统。\u003c/p\u003e\n\u003cp\u003e本机写md文件,更新到\u003ccode\u003egithub\u003c/code\u003e,然后通过\u003ccode\u003etravis-ci\u003c/code\u003e自动发布。\u003c/p\u003e\n\u003cp\u003ejane主题是通过\u003ccode\u003egit submodule\u003c/code\u003e引入的,\u003ccode\u003e.gitmodules\u003c/code\u003e文件内容。\u003c/p\u003e","content_html":"\u003ch4 id=\"背景\"\u003e背景\u003c/h4\u003e\n\u003cp\u003e1月份的时候,用\u003ccode\u003ehugo\u003c/code\u003e搭了这套博客系统。\u003c/p\u003e\n\u003cp\u003e本机写md文件,更新到\u003ccode\u003egithub\u003c/code\u003e,然后通过\u003ccode\u003etravis-ci\u003c/code\u003e自动发布。\u003c/p\u003e\n\u003cp\u003ejane主题是通过\u003ccode\u003egit submodule\u003c/code\u003e引入的,\u003ccode\u003e.gitmodules\u003c/code\u003e文件内容。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[submodule \u0026#34;themes/jane\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tpath = themes/jane\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\turl = https://github.com/xianmin/hugo-theme-jane.git\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"问题\"\u003e问题\u003c/h4\u003e\n\u003cp\u003e最近几天更新完文章后,发现首页显示出了问题。\u003c/p\u003e\n\u003cp\u003e一开始以为是主题有问题,具体描述见\u003ca href=\"https://github.com/xianmin/hugo-theme-jane/issues/244\"\u003e首页文章不显示了\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eissue\u003c/code\u003e里:\n\u003ccode\u003eshaform\u003c/code\u003e提到使用的并不是最新的版本。\n\u003ccode\u003eRocFang\u003c/code\u003e提到是\u003ccode\u003egit submodule\u003c/code\u003e使用的问题。\u003c/p\u003e\n\u003cp\u003e但是\u003ccode\u003etravis-ci\u003c/code\u003e每次都是通过\u003ccode\u003egit submodule update --init --recursive\u003c/code\u003e更新子仓库代码的,为什么会不是最新的代码呢。\u003c/p\u003e\n\u003ch4 id=\"问题重现\"\u003e问题重现\u003c/h4\u003e\n\u003cp\u003e接下来,我们用一个新的仓库,来模拟重现一下。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e克隆仓库。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos xx]# git clone git@github.com:Liudon/test.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;test\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos xx]# cd test/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# echo \u0026#34;# test\u0026#34; \u0026gt;\u0026gt; README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# git add README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e引用子仓库。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# git submodule add git@github.com:xianmin/hugo-theme-jane.git theme/jane\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Cloning into \u0026#39;theme/jane\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e remote: Enumerating objects: 216, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e remote: Counting objects: 100% (216/216), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e remote: Compressing objects: 100% (128/128), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e remote: Total 6165 (delta 102), reused 159 (delta 65), pack-reused 5949\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Receiving objects: 100% (6165/6165), 3.05 MiB | 1.70 MiB/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Resolving deltas: 100% (3443/3443), done.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e查看文件列表。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# ll\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e total 8\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e -rw-r--r-- 1 root root 5 Sep 6 16:05 README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e drwxr-xr-x 7 root root 4096 Sep 6 16:08 typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e查看状态。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# git status\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# On branch master\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# Initial commit\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# Changes to be committed:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e# (use \u0026#34;git rm --cached \u0026lt;file\u0026gt;...\u0026#34; to unstage)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\tnew file: .gitmodules\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\tnew file: README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\tnew file: typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e查看修改。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# git diff --cached\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ediff --git a/.gitmodules b/.gitmodules\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enew file mode 100644\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eindex 0000000..b1ddf70\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e--- /dev/null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+++ b/.gitmodules\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@@ -0,0 +1,3 @@\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+[submodule \u0026#34;typecho\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+ path = typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+ url = https://github.com/Liudon/typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ediff --git a/README.md b/README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enew file mode 100644\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eindex 0000000..9daeafb\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e--- /dev/null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+++ b/README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@@ -0,0 +1 @@\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ediff --git a/typecho b/typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enew file mode 160000\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eindex 0000000..b0c4cc7\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e--- /dev/null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+++ b/typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@@ -0,0 +1 @@\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e+Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e注意最后一行\u003ccode\u003eSubproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e这个\u003ccode\u003ecommitId\u003c/code\u003e是子仓库最新提交的记录id,对应的\u003ca href=\"https://github.com/Liudon/typecho/commit/b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\"\u003e修改记录\u003c/a\u003e。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e提交修改。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# git push -u origin master\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCounting objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCompressing objects: 100% (3/3), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWriting objects: 100% (4/4), 362 bytes | 0 bytes/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTotal 4 (delta 0), reused 0 (delta 0)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTo git@github.com:Liudon/test.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* [new branch] master -\u0026gt; master\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBranch master set up to track remote branch master from origin.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003cp\u003e提交后,在github上子仓库后面会多显示一个\u003ccode\u003e@xxxxx\u003c/code\u003e,这里就是引用的\u003ccode\u003ecommitId\u003c/code\u003e,对应到前面\u003ccode\u003egit diff\u003c/code\u003e最后一行。\u003c/p\u003e\n\u003cp\u003e点击查看\u003ca href=\"https://github.com/Liudon/test/commit/5b11d515db8ad8d299ef1691f115590e0015c3b7\"\u003e提交记录\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003c/p\u003e\n\u003cp\u003e本次提交的\u003ccode\u003ecommitId\u003c/code\u003e为\u003ccode\u003e5b11d515db8ad8d299ef1691f115590e0015c3b7\u003c/code\u003e,子仓库typecho单独记录了引入时的\u003ccode\u003ecommitId\u003c/code\u003e,为\u003ccode\u003eb0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\u003c/code\u003e,对应的\u003ca href=\"https://github.com/Liudon/typecho/tree/b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\"\u003e提交记录\u003c/a\u003e。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e接下来克隆子仓库,进行更新提交。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos xx]# git clone git@github.com:Liudon/typecho.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;typecho\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Enumerating objects: 1, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Counting objects: 100% (1/1), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Total 7179 (delta 0), reused 0 (delta 0), pack-reused 7178\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving objects: 100% (7179/7179), 7.26 MiB | 2.02 MiB/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eResolving deltas: 100% (4844/4844), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos xx]# \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos xx]# cd typecho/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# git log -n 1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecommit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMerge: c904005 8fd7492\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAuthor: 祁宁 \u0026lt;magike.net@gmail.com\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Tue Nov 18 13:59:52 2014 +0800\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Merge branch \u0026#39;master\u0026#39; of https://github.com/typecho/typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]#\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过\u003ccode\u003egit log\u003c/code\u003e,确认最新的提交commitId为\u003ccode\u003eb0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\u003c/code\u003e,与前面的引入的一致。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# echo \u0026#34;xxx\u0026#34; \u0026gt; test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# git add test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# git commit -m \u0026#39;test\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[master 5dcc8f4] test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e1 file changed, 1 insertion(+)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecreate mode 100644 test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# git push\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCounting objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCompressing objects: 100% (2/2), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eWriting objects: 100% (3/3), 252 bytes | 0 bytes/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTotal 3 (delta 1), reused 0 (delta 0)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Resolving deltas: 100% (1/1), completed with 1 local object.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTo git@github.com:Liudon/typecho.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eb0c4cc7..5dcc8f4 master -\u0026gt; master\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e修改文件提交。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]# git log -n 1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecommit 5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAuthor: liudon \u0026lt;i.mu@qq.com\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDate: Fri Sep 6 16:26:47 2019 +0800\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e test\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos typecho]#\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e最新提交的\u003ccode\u003ecommitId\u003c/code\u003e为\u003ccode\u003e5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2\u003c/code\u003e。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e重新克隆\u003ccode\u003etest\u003c/code\u003e库。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;test\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Enumerating objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Counting objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Compressing objects: 100% (3/3), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# cd test/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# ll\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etotal 8\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-r--r-- 1 root root 5 Sep 6 16:31 README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edrwxr-xr-x 2 root root 4096 Sep 6 16:31 typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# ll typecho/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etotal 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里可以看到\u003ccode\u003etypecho\u003c/code\u003e目录下是没有文件的。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# git submodule update --init --recursive\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSubmodule \u0026#39;typecho\u0026#39; (https://github.com/Liudon/typecho) registered for path \u0026#39;typecho\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;typecho\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Enumerating objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Counting objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Compressing objects: 100% (3/3), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving objects: 100% (7182/7182), 7.26 MiB | 1.26 MiB/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eResolving deltas: 100% (4844/4844), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSubmodule path \u0026#39;typecho\u0026#39;: checked out \u0026#39;b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]#\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e更新子仓库代码,这里可以看到最终\u003ccode\u003echeckout\u003c/code\u003e的版本为\u003ccode\u003eb0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1\u003c/code\u003e,与前面提交时的版本一致。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch4 id=\"问题分析\"\u003e问题分析\u003c/h4\u003e\n\u003cp\u003e\u003ccode\u003egit submodule add\u003c/code\u003e的时候,会记录当时引入时子仓库的版本id。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003egit submodule update --init --recursive\u003c/code\u003e,会检出引入时的仓库版本,这就是为啥代码没有更新了。\u003c/p\u003e\n\u003ch4 id=\"问题解决\"\u003e问题解决\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;test\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Enumerating objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Counting objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Compressing objects: 100% (3/3), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos yy]# cd test/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# ll\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etotal 8\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-rw-r--r-- 1 root root 5 Sep 6 16:37 README.md\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edrwxr-xr-x 2 root root 4096 Sep 6 16:37 typecho\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# ll typecho/\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etotal 0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]# git submodule update --init --remote --recursive\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSubmodule \u0026#39;typecho\u0026#39; (https://github.com/Liudon/typecho) registered for path \u0026#39;typecho\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCloning into \u0026#39;typecho\u0026#39;...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Enumerating objects: 4, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Counting objects: 100% (4/4), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Compressing objects: 100% (3/3), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eremote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReceiving objects: 100% (7182/7182), 7.26 MiB | 1.24 MiB/s, done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eResolving deltas: 100% (4844/4844), done.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSubmodule path \u0026#39;typecho\u0026#39;: checked out \u0026#39;5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_81_18_centos test]#\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e使用\u003ccode\u003egit submodule update --init --remote --recursive\u003c/code\u003e命令。\u003c/p\u003e\n","date_published":"2019-09-06T15:13:51+08:00","tags":["git"]},{"title":"一个Curl的耗时长的问题","id":"https://liudon.com/posts/curl-cost-time-long/","url":"https://liudon.com/posts/curl-cost-time-long/","summary":"\u003cp\u003e发现某个接口请求很慢,但是后端确认接口是很快的。\u003c/p\u003e\n\u003cp\u003e在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。\u003c/p\u003e\n\u003cp\u003e业务里用到了\u003ccode\u003eRequests\u003c/code\u003e这个库,一开始以为是这个库导致的问题。\u003c/p\u003e","content_html":"\u003cp\u003e发现某个接口请求很慢,但是后端确认接口是很快的。\u003c/p\u003e\n\u003cp\u003e在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。\u003c/p\u003e\n\u003cp\u003e业务里用到了\u003ccode\u003eRequests\u003c/code\u003e这个库,一开始以为是这个库导致的问题。\u003c/p\u003e\n\u003cp\u003e在\u003ccode\u003eRequests_Transport_cURL\u003c/code\u003e类里断点定位了下,确实很慢,\u003ccode\u003ecurl_getinfo\u003c/code\u003e返回的信息如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003earray (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;url\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;http://xxxxx\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;content_type\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;text/html\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;http_code\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;header_size\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e64\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;request_size\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e305\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;filetime\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;ssl_verify_result\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;redirect_count\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;total_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2.074094\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;namelookup_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2.5E-5\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;connect_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0.032107\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;pretransfer_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0.032109\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;size_upload\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e186\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;size_download\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e99\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;speed_download\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e47\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;speed_upload\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e89\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;download_content_length\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e99\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;upload_content_length\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e186\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;starttransfer_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2.032866\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;redirect_time\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;certinfo\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e array (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里可以看到\u003ccode\u003estarttransfer_time\u003c/code\u003e时间很长。\u003c/p\u003e\n\u003cp\u003e搜索了一番,发现网上一个case,\u003ca href=\"https://stackoverflow.com/questions/20428632/curl-slow-starttransfer-time\"\u003ecURL slow starttransfer_time\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e里面提供了\u003ccode\u003eExpect: 100-continue\u003c/code\u003e这个header,又搜索了一番这个header资料。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecurl\u003c/code\u003e在发\u003ccode\u003ePOST\u003c/code\u003e请求的时候,如果body大于1k:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e先追加一个\u003ccode\u003eExpect: 100-continue\u003c/code\u003e请求头信息,发送这个不包含 \u003ccode\u003ePOST\u003c/code\u003e 数据的请求;\u003c/li\u003e\n\u003cli\u003e如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server;\n如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e在机器上抓了个包,执行下面命令。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e注意,下面port后面的80改成实际的端口\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etcpdump -A -s 0 \u0026#39;tcp port 80 and (((ip[2:2] - ((ip[0]\u0026amp;0xf)\u0026lt;\u0026lt;2)) - ((tcp[12]\u0026amp;0xf0)\u0026gt;\u0026gt;2)) != 0)\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e拿到的包信息。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e09:17:19.421587 IP xxx.54360 \u0026gt; xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eE..f.m@.@...d}@. A...XF.-.@...h....s.......\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e....T.0TPOST /cgi HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eUser-Agent: php-requests/1.6\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccept: */*\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eAccept-Encoding: deflate, gzip\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eReferer: http://xxxxx:12345/cgi\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 188\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExpect: 100-continue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: multipart/form-data; boundary=----------------------------ee2f4d848646\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e09:17:21.421786 IP xxx.54360 \u0026gt; xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eE....n@.@..Md}@. A...XF.-.B...h....s./.....\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e....T.0[------------------------------ee2f4d848646\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Disposition: form-data; name=\u0026#34;req\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\u0026#34;command\u0026#34;:\u0026#34;zzz\u0026#34;,\u0026#34;appId\u0026#34;:\u0026#34;yyyy\u0026#34;}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e------------------------------ee2f4d848646--\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e09:17:21.458628 IP xxxxx:12345 \u0026gt; xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eE...X.@.5.Q2 A..d}@.F..X..h.-.B......3.....\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eT.2Q....HTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eContent-Length: 53\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;data\u0026#34;: [],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;errno\u0026#34;: 0,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#34;error\u0026#34;: \u0026#34;ok\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看到确实是先发了一个\u003ccode\u003e100-continue\u003c/code\u003e的请求,然后再发的实际\u003ccode\u003ePOST\u003c/code\u003e请求。\u003c/p\u003e\n\u003cp\u003e在机器上执行下面的shell命令。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl \u0026#39;http://xxxxx:12345/cgi\u0026#39; -H\u0026#34;Expect: 100-continue\u0026#34; -v\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e返回如下,可以看到返回的header头里确实没有\u003ccode\u003eExpect\u003c/code\u003e这项。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* About to connect() to xxxxx port 12345 (#0)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* Trying xxxxx...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* Connected to xxxxx (xxxxx) port 12345 (#0)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; GET /cloud_cgi HTTP/1.1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; User-Agent: curl/7.29.0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Host: xxxxx:12345\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Accept: */*\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; Expect: 100-continue\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt; HTTP/1.1 200 OK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt; Content-Type: text/html\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt; Content-Length: 42\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt; \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e* Connection #0 to host xxxxx left intact\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\u0026#34;errno\u0026#34;:100,\u0026#34;error\u0026#34;:\u0026#34;参数格式错误\u0026#34;}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e解决方法:\u003c/p\u003e\n\u003cp\u003e请求的时候,header里新增一项。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eExpect:\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","date_published":"2019-09-04T11:07:46+08:00","tags":["php","curl"]},{"title":"Swoft 框架运行分析(三) —— BeanProcessor模块分析","id":"https://liudon.com/posts/swoft-bean-processor-analysis/","url":"https://liudon.com/posts/swoft-bean-processor-analysis/","summary":"\u003cp\u003e今天讲一下\u003ccode\u003eBeanProcessor\u003c/code\u003e模块,先看一下\u003ccode\u003ehandle\u003c/code\u003e方法实现。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeBean()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehandler \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new BeanHandler();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDefinitions();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getParsers();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getAnnotations();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addDefinitions(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addAnnotations(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addParsers(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::setHandler(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehandler);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::init();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Config \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeanFactory::getBean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config path=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPath());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config env=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetEnv());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeanFactory::getStats();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Bean is initialized(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e)\u0026#39;\u003c/span\u003e, SwoftHelper::formatStats(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterBean();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e先通过\u003ccode\u003egetDefinitions\u003c/code\u003e方法获取所有的Bean定义。\u003c/p\u003e","content_html":"\u003cp\u003e今天讲一下\u003ccode\u003eBeanProcessor\u003c/code\u003e模块,先看一下\u003ccode\u003ehandle\u003c/code\u003e方法实现。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeBean()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehandler \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new BeanHandler();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDefinitions();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getParsers();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getAnnotations();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addDefinitions(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addAnnotations(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::addParsers(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::setHandler(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ehandler);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BeanFactory::init();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e Config \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeanFactory::getBean(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config path=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPath());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;config env=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetEnv());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e BeanFactory::getStats();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Bean is initialized(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e)\u0026#39;\u003c/span\u003e, SwoftHelper::formatStats(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterBean();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e先通过\u003ccode\u003egetDefinitions\u003c/code\u003e方法获取所有的Bean定义。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Get bean definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function getDefinitions(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Core beans\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoaders \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getAutoLoaders();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e get disabled loaders by application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edisabledLoaders \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDisabledAutoLoaders();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoaders as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoader) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eautoLoader instanceof DefinitionInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e get_class(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoader);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e If the component is disabled by user\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edisabledLoaders[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Auto loader(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e) is \u0026lt;cyan\u0026gt;disabled\u0026lt;/cyan\u0026gt;, skip handle it\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e If the component is \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e enabled\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoader instanceof ComponentInterface \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eautoLoader\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisEnable()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ArrayHelper::merge(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoLoader\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeans());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Bean definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetBeanFile();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e alias(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003efile_exists(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e sprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;The bean config file of \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e is not exist!\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanDefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e require \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanFile;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e ArrayHelper::merge(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanDefinitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefinitions;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过\u003ccode\u003eAnnotationRegister::getAutoLoaders()\u003c/code\u003e拿到所有的autoloader对象,排除掉非\u003ccode\u003eDefinitionInterface\u003c/code\u003e对象,通过\u003ccode\u003ebean()\u003c/code\u003e方法获取定义的Bean信息。\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003ehttp-server\\src\\AutoLoader.php\u003c/code\u003e为例。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Http\\Server;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function dirname;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse ReflectionException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Exception\\ContainerException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Helper\\ComposerJSON;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Message\\ContentType;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Message\\Response;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Formatter\\HtmlResponseFormatter;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Formatter\\JsonResponseFormatter;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Formatter\\XmlResponseFormatter;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Parser\\JsonRequestParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Parser\\XmlRequestParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Http\\Server\\Swoole\\RequestListener;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Server\\SwooleEvent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\SwoftComponent;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class AutoLoader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e AutoLoader \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e SwoftComponent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Metadata information \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e the component\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esee ComponentInterface::getMetadata()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function metadata(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ejsonFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e dirname(__DIR__) \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/composer.json\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e ComposerJSON::open(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ejsonFile)\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetMetadata();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Get namespace \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e dirs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getPrefixDirs(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __NAMESPACE__ \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e __DIR__,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ContainerException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function beans(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpRequest\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;parsers\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ContentType::XML \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(XmlRequestParser::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ContentType::JSON \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(JsonRequestParser::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpResponse\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;format\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e Response::FORMAT_JSON,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;formatters\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Response::FORMAT_HTML \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(HtmlResponseFormatter::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Response::FORMAT_JSON \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(JsonResponseFormatter::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Response::FORMAT_XML \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(XmlResponseFormatter::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;acceptFormatter\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;formats\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ContentType::JSON \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e Response::FORMAT_JSON,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ContentType::HTML \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e Response::FORMAT_HTML,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ContentType::XML \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e Response::FORMAT_XML,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpServer\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;on\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e SwooleEvent::REQUEST \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e bean(RequestListener::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;httpRouter\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;swoft-http-router\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e config\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;ignoreLastSlash\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;tmpCacheNumber\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e500\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看到,这里通过\u003ccode\u003ebeans()\u003c/code\u003e定义了\u003ccode\u003ehttpRequest\u003c/code\u003e、\u003ccode\u003ehttpResponse\u003c/code\u003e、\u003ccode\u003eacceptFormatter\u003c/code\u003e、\u003ccode\u003ehttpServer\u003c/code\u003e和\u003ccode\u003ehttpRouter\u003c/code\u003e四个Bean对象。\u003c/p\u003e\n\u003cp\u003e回到上面\u003ccode\u003egetDefinitions\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e$definitions = ArrayHelper::merge($definitions, $autoLoader-\u0026gt;beans());\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e然后将Bean信息添加到\u003ccode\u003edefinitions\u003c/code\u003e对象上。\u003c/p\u003e\n\u003cp\u003e之后通过\u003ccode\u003e$beanFile = $this-\u0026gt;application-\u0026gt;getBeanFile();\u003c/code\u003e获取bean配置文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$beanDefinitions = require $beanFile;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$definitions = ArrayHelper::merge($definitions, $beanDefinitions);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e加载配置文件,然后将Bean信息添加到\u003ccode\u003edefinitions\u003c/code\u003e对象上。\u003c/p\u003e\n\u003cp\u003e可以看到Bean有两种定义方式:通过AutoLoader和配置文件,与\u003ca href=\"https://en.swoft.org/docs/2.x/zh-CN/bean/bean.html\"\u003eswoft官方文档\u003c/a\u003e里的说明一致。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003ehandle\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$parsers = AnnotationRegister::getParsers();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$annotations = AnnotationRegister::getAnnotations();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e还记得上一篇文章最后提到的\u003ccode\u003eAnnotationRegister\u003c/code\u003e类的\u003ccode\u003eannotations\u003c/code\u003e和\u003ccode\u003eparsers\u003c/code\u003e两个属性吗?这里通过\u003ccode\u003egetParsers\u003c/code\u003e和\u003ccode\u003egetAnnotations\u003c/code\u003e获取这两个属性。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeanFactory::addDefinitions($definitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeanFactory::addAnnotations($annotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeanFactory::addParsers($parsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeanFactory::setHandler($handler);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eBeanFactory::init();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e向BeanFatory注册信息。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function init(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Container::getInstance()-\u0026gt;init();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function addDefinitions(array $definitions): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Container::getInstance()-\u0026gt;addDefinitions($definitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function addAnnotations(array $annotations): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Container::getInstance()-\u0026gt;addAnnotations($annotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add annotation parsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $annotationParsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function addParsers(array $annotationParsers): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Container::getInstance()-\u0026gt;addParsers($annotationParsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Set bean handler\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param HandlerInterface $handler\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function setHandler(HandlerInterface $handler): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Container::getInstance()-\u0026gt;setHandler($handler);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里可以看到所有的方法,最终都调用的是\u003ccode\u003eSwoft\\Bean\\Container\u003c/code\u003e类。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function addDefinitions(array $definitions): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;definitions = ArrayHelper::merge($this-\u0026gt;definitions, $definitions);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function addAnnotations(array $annotations): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;annotations = ArrayHelper::merge($this-\u0026gt;annotations, $annotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Add annotation parsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $annotationParsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function addParsers(array $annotationParsers): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;parsers = ArrayHelper::merge($this-\u0026gt;parsers, $annotationParsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param HandlerInterface $handler\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function setHandler(HandlerInterface $handler): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;handler = $handler;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这四个方法就是注册属性,接下来是重头戏\u003ccode\u003einit\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function init(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Parse annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;parseAnnotations();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Parse definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;parseDefinitions();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Init beans\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;initializeBeans();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e先看\u003ccode\u003eparseAnnotations\u003c/code\u003e方法,从代码注释上也可以看出大概,解析注解,接下来我们看下具体是如何实现的。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Parse annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseAnnotations(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $annotationParser = new AnnotationObjParser(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;definitions, $this-\u0026gt;objectDefinitions, $this-\u0026gt;classNames, $this-\u0026gt;aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $annotationData = $annotationParser-\u0026gt;parseAnnotations($this-\u0026gt;annotations, $this-\u0026gt;parsers);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [$this-\u0026gt;definitions, $this-\u0026gt;objectDefinitions, $this-\u0026gt;classNames, $this-\u0026gt;aliases] = $annotationData;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e声明了一个\u003ccode\u003eAnnotationObjParser\u003c/code\u003e对象,调用了\u003ccode\u003eparseAnnotations\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Parse annotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function parseAnnotations(array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations, array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparsers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparsers;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eannotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eannotations as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloadNameSpace \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclasses) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclasses as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassOneAnnotations) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparseOneClassAnnotations(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassOneAnnotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edefinitions, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里遍历所有的\u003ccode\u003eannotation\u003c/code\u003e类,循环调用\u003ccode\u003eparseOneClassAnnotations\u003c/code\u003e进行解析。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-zed\" data-lang=\"zed\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * Parse class all annotations\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param string $className\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param array $classOneAnnotations\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseOneClassAnnotations(string \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName, array \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Check class annotation tag\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eisset(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eannotation\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new AnnotationException(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e sprintf(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eProperty or method(\u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003es) with \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`@\u003c/span\u003exxx\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e`\u003c/span\u003e must be define class annotation\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Parse class annotations\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAnnotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eannotation\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ereflectionClass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003ereflection\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAry \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ereflectionClass,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAnnotations\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparseClassAnnotations(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAry);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Parse property annotations\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyAllAnnotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eproperties\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyAllAnnotations as \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyOneAnnotations) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproAnnotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eannotation\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparsePropertyAnnotations(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAry, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproAnnotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Parse method annotations\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInjects \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodAllAnnotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003emethods\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodAllAnnotations as \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodOneAnnotations) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodAnnotations \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodOneAnnotations[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eannotation\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparseMethodAnnotations(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassAry, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodName, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodAnnotations);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInject) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInjects[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetPropertyInjections(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInjects)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetMethodInjections(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003emethodInjects);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Object definition and class name\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealiase \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAlias();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ename;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e array_unique(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ename] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjectDefinition;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealiase)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealiase] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ename;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里可以看到分别有类注解、属性注解和方法注解三类。\u003c/p\u003e\n\u003cp\u003e对应官方文档的\u003ca href=\"https://en.swoft.org/docs/2.x/zh-CN/annotation/index.html#%E8%A7%84%E8%8C%83\"\u003e注解说明\u003c/a\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param array $classAry\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return ObjectDefinition|null\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseClassAnnotations(array $classAry): ?ObjectDefinition\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [, , $classAnnotations] = $classAry;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $objectDefinition = null;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($classAnnotations as $annotation) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $annotationClass = get_class($annotation);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!isset($this-\u0026gt;parsers[$annotationClass])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e continue;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $parserClassName = $this-\u0026gt;parsers[$annotationClass];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $annotationParser = $this-\u0026gt;getAnnotationParser($classAry, $parserClassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $data = $annotationParser-\u0026gt;parse(Parser::TYPE_CLASS, $annotation);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (empty($data)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e continue;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (count($data) !== 4) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(sprintf(\u0026#39;%s annotation parse must be 4 size\u0026#39;, $annotationClass));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [$name, $className, $scope, $alias] = $data;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $name = empty($name) ? $className : $name;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (empty($className)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(sprintf(\u0026#39;%s with class name can not be empty\u0026#39;, $annotationClass));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Multiple coverage\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return $objectDefinition;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e类注解,这里会调用对应解析类的\u003ccode\u003eparse\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e这里以\u003ccode\u003ewebsocket-server\\src\\Annotation\\Mapping\\WsModule.php\u003c/code\u003e和\u003ccode\u003ewebsocket-server\\src\\Annotation\\Parser\\WsModuleParser.php\u003c/code\u003e为例。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\WebSocket\\Server\\Annotation\\Mapping;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Doctrine\\Common\\Annotations\\Annotation\\Attribute;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Doctrine\\Common\\Annotations\\Annotation\\Attributes;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Doctrine\\Common\\Annotations\\Annotation\\Required;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Doctrine\\Common\\Annotations\\Annotation\\Target;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\WebSocket\\Server\\MessageParser\\RawTextParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class WebSocket \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e mark an websocket module handler \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAnnotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eTarget(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;CLASS\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAttributes(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e, type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;string\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;path\u0026#34;\u003c/span\u003e, type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;string\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;controllers\u0026#34;\u003c/span\u003e, type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;array\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAttribute(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;messageParser\u0026#34;\u003c/span\u003e, type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;string\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efinal \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e WsModule\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Websocket route path\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e(it must unique \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e a application)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eRequired()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Module name\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Routing path params binding\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e eg\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e {\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\\d+\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eparams \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Message controllers of the module\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e string[]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econtrollers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Message parser \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e the module\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003emessageParser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e RawTextParser::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Default message command\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e Format \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;controller.action\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefaultCommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;home.index\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Default message opcode \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e response\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e please see WEBSOCKET_OPCODE_\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edefaultOpcode \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class constructor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function __construct(array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003epath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (string)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;value\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e } elseif (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;path\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003epath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (string)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;path\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ename \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (string)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;params\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparams \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (array)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;params\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;controllers\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003econtrollers \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (array)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;controllers\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;messageParser\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003emessageParser \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;messageParser\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultOpcode\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edefaultOpcode \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultOpcode\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultCommand\u0026#39;\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edefaultCommand \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003evalues[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultCommand\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getPath(): string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003epath;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getMessageParser(): string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003emessageParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getDefaultCommand(): string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edefaultCommand;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getName(): string\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ename;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e string[]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getControllers(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003econtrollers;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getParams(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparams;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function getDefaultOpcode(): \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edefaultOpcode;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eWsModule\u003c/code\u003e声明了一个类注解。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\WebSocket\\Server\\Annotation\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Mapping\\AnnotationParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Annotation\\Parser\\Parser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\Exception\\AnnotationException;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Bean\\Annotation\\Mapping\\Bean;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Stdlib\\Helper\\Str;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\WebSocket\\Server\\Annotation\\Mapping\\WsModule;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\WebSocket\\Server\\MessageParser\\RawTextParser;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\WebSocket\\Server\\Router\\RouteRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class WebSocketParser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eAnnotationParser(WsModule::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e WsModuleParser \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e Parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Parse object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype Class \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e Method \u003cspan style=\"color:#f92672\"\u003eor\u003c/span\u003e Property\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam WsModule \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann Annotation object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Return empty array is nothing to \u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e When \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e type \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealias, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esize] is to inject bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e When property type \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epropertyValue, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eisRef] is to reference value\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function parse(\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype \u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003e self::TYPE_CLASS) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new AnnotationException(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;`@WsModule` must be defined on class!\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e RouteRegister::bindModule(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e, [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;path\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPath() \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e: Str::getClassName(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Module\u0026#39;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;name\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;params\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetParams(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;class\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;eventMethods\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;controllers\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetControllers(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;messageParser\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetMessageParser() \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003e: RawTextParser::\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultOpcode\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDefaultOpcode(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;defaultCommand\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eann\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDefaultCommand(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e, Bean::SINGLETON, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e按上一篇文章说明,这里\u003ccode\u003eWsModuleParser\u003c/code\u003e会被标记为注解类\u003ccode\u003eWsModule\u003c/code\u003e的注解解析类。\u003c/p\u003e\n\u003cp\u003e解析注解的时候,会调用\u003ccode\u003eWsModuleParser\u003c/code\u003e的\u003ccode\u003eparse\u003c/code\u003e方法,这里通过\u003ccode\u003eRouteRegister::bindModule\u003c/code\u003e做了一些路由操作,这里后续再讲,这里不做深入介绍。\u003c/p\u003e\n\u003cp\u003e属性和方法注解,也是类似的,\u003ccode\u003eparseAnnotations\u003c/code\u003e方法就讲完了。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eContainer\u003c/code\u003e类的\u003ccode\u003einit\u003c/code\u003e方法,接下来调用了\u003ccode\u003eparseDefinitions\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Parse definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseDefinitions(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $annotationParser = new DefinitionObjParser(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;definitions, $this-\u0026gt;objectDefinitions, $this-\u0026gt;classNames, $this-\u0026gt;aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Collect info\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $definitionData = $annotationParser-\u0026gt;parseDefinitions();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [$this-\u0026gt;definitions, $this-\u0026gt;objectDefinitions, $this-\u0026gt;classNames, $this-\u0026gt;aliases] = $definitionData;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e声明了一个\u003ccode\u003eDefinitionObjParser\u003c/code\u003e对象,调用了\u003ccode\u003eparseDefinitions\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Parse definitions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function parseDefinitions(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($this-\u0026gt;definitions as $beanName =\u0026gt; $definition) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (isset($this-\u0026gt;objectDefinitions[$beanName])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $objectDefinition = $this-\u0026gt;objectDefinitions[$beanName];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;resetObjectDefinition($beanName, $objectDefinition, $definition);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e continue;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;createObjectDefinition($beanName, $definition);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [$this-\u0026gt;definitions, $this-\u0026gt;objectDefinitions, $this-\u0026gt;classNames, $this-\u0026gt;aliases];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e遍历所有的\u003ccode\u003eBean\u003c/code\u003e对象,调用\u003ccode\u003ecreateObjectDefinition\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-zed\" data-lang=\"zed\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * Create object definition for definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param string $beanName\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param array $definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function createObjectDefinition(string \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ebeanName, array \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eclass\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (empty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(sprintf(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e%\u003c/span\u003es key for \u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e must be defined class\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ebeanName));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDefinition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new ObjectDefinition(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ebeanName, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDefinition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eupdateObjectDefinitionByDefinition(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDefinition, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ebeanName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassNames[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e array_unique(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eclassNames);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ebeanName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDefinition;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e声明了\u003ccode\u003eObjectDefinition\u003c/code\u003e对象,调用了\u003ccode\u003eupdateObjectDefinitionByDefinition\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-zed\" data-lang=\"zed\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * Update definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param ObjectDefinition $objDfn\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param array $definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @return ObjectDefinition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function updateObjectDefinitionByDefinition(ObjectDefinition \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn, array \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e ObjectDefinition\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eoption] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparseDefinition(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Set construct inject\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetConstructorInjection(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Set property inject\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e foreach (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects as \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetPropertyInjection(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escopes \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Bean\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eSINGLETON,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Bean\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ePROTOTYPE,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Bean\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eREQUEST,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escope \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eoption[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003escope\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eoption[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003ealias\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escope) \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003ein_array(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escope, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escopes, true)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new InvalidArgumentException(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003eScope for \u003cspan style=\"color:#66d9ef\"\u003edefinition\u003c/span\u003e is not undefined\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Update scope\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escope)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetScope(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003escope);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Update alias\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e if (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealias)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetAlias(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealias);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjAlias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAlias();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e unset(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjAlias]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ealias] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eobjDfn;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里调用了\u003ccode\u003eparseDefinition\u003c/code\u003e方法进行解析。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-mysql\" data-lang=\"mysql\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * Parse definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @param array $definition\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e * @return array\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function \u003cspan style=\"color:#a6e22e\"\u003eparseDefinition\u003c/span\u003e(array \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Remove class \u003cspan style=\"color:#66d9ef\"\u003ekey\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#a6e22e\"\u003eunset\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;class\u0026#39;\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse construct\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructArgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eis_array\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructArgs)) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new \u003cspan style=\"color:#a6e22e\"\u003eInvalidArgumentException\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Construct args for definition must be array\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse construct args\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargInjects \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#a6e22e\"\u003eforeach\u003c/span\u003e (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructArgs \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003earg) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargValue, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargIsRef] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003egetValueByRef\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003earg);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargInjects[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new \u003cspan style=\"color:#a6e22e\"\u003eArgsInjection\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargValue, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargIsRef);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eSet\u003c/span\u003e construct inject\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enull\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eempty\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargInjects)) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new \u003cspan style=\"color:#a6e22e\"\u003eMethodInjection\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__construct\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eargInjects);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Remove construct definition\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#a6e22e\"\u003eunset\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse definition \u003cspan style=\"color:#66d9ef\"\u003eoption\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eoption\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__option\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e??\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eis_array\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eoption\u003c/span\u003e)) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new \u003cspan style=\"color:#a6e22e\"\u003eInvalidArgumentException\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__option for definition must be array\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Remove \u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e__option\u003cspan style=\"color:#f92672\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#a6e22e\"\u003eunset\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;__option\u0026#39;\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse definition properties\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#a6e22e\"\u003eforeach\u003c/span\u003e (\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003edefinition \u003cspan style=\"color:#66d9ef\"\u003eas\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyValue) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eis_string\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName)) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e throw new \u003cspan style=\"color:#a6e22e\"\u003eInvalidArgumentException\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Property key from definition must be string\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproValue, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproIsRef] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003egetValueByRef\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyValue);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse property \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003eis_array\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproValue)) \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproValue \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eparseArrayProperty\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproValue);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new \u003cspan style=\"color:#a6e22e\"\u003ePropertyInjection\u003c/span\u003e(\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproValue, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003eproIsRef);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects[\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInject;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003econstructInject, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003epropertyInjects, \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eoption\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e解析\u003ccode\u003e__construct\u003c/code\u003e方法和传参,解析属性信息。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eupdateObjectDefinitionByDefinition\u003c/code\u003e方法,将\u003ccode\u003e__construct\u003c/code\u003e和类属性信息注册到\u003ccode\u003eObjectDefinition\u003c/code\u003e对象上,到这里\u003ccode\u003eparseDefinitions\u003c/code\u003e方法执行完毕。\u003c/p\u003e\n\u003cp\u003e回到\u003ccode\u003eContainer\u003c/code\u003e类的\u003ccode\u003einit\u003c/code\u003e方法,接下来调用了\u003ccode\u003einitializeBeans\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Initialize beans\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows InvalidArgumentException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function initializeBeans(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ObjectDefinition \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetScope();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Exclude request\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e Bean::REQUEST) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003erequestDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e unset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Exclude session\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e Bean::SESSION) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esessionDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e unset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eobjectDefinitions[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e New bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enewBean(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e对于\u003ccode\u003escope\u003c/code\u003e不为\u003ccode\u003eBean::REQUEST\u003c/code\u003e和\u003ccode\u003eBean::SESSION\u003c/code\u003e的,调用\u003ccode\u003enewBean\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Initialize beans\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function newBean(string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName, string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e First, check bean whether has been create\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esingletonPool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName]) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprototypePool[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eget(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Get object definition\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetNewObjectDefinition(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetScope();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAlias();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetClassName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Cache reflection \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e info\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Reflections::cache(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Before initialize bean\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeInit(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructArgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructInject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetConstructorInjection();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructInject \u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003e null) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructArgs \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetConstructParams(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructInject, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epropertyInjects \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eobjectDefinition\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPropertyInjections();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Proxy \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehandler) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehandler\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassProxy(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectionClass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new ReflectionClass(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectObject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enewInstance(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectionClass, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econstructArgs);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Inject properties values\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enewProperty(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectObject, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectionClass, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epropertyInjects, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Alias\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eempty(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealias)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ealiases[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ealias] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Call init method \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e exist\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectionClass\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehasMethod(self::INIT_METHOD)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectObject\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e{self::INIT_METHOD}();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetNewBean(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ebeanName, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003escope, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ereflectObject, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过反射实例化\u003ccode\u003eBean\u003c/code\u003e对应的类,注册对应的属性。\u003c/p\u003e\n\u003cp\u003e如果类存在\u003ccode\u003eself::INIT_METHOD\u003c/code\u003e方法,执行此方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $beanName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $scope\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param object $object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $id\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return object\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function setNewBean(string $beanName, string $scope, $object, string $id = \u0026#39;\u0026#39;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e switch ($scope) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e case Bean::SINGLETON: // Singleton\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;singletonPool[$beanName] = $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e break;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e case Bean::PROTOTYPE:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;prototypePool[$beanName] = $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Clone it\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $object = clone $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e break;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e case Bean::REQUEST:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;requestPool[$id][$beanName] = $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e break;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e case Bean::SESSION:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;sessionPool[$id][$beanName] = $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e break;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return $object;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003esetNewBean\u003c/code\u003e方法,根据对应的scope信息,将实例化后的反射类注册到对应的类属性上。\u003c/p\u003e\n\u003cp\u003e到这里\u003ccode\u003eBeanProcessor\u003c/code\u003e类就执行完了。\u003c/p\u003e\n","date_published":"2019-09-02T18:29:06+08:00","tags":["Swoft"]},{"title":"Swoft 框架运行分析(二) —— AnnotationProcessor模块分析","id":"https://liudon.com/posts/swoft-anaotion-processor-analysis/","url":"https://liudon.com/posts/swoft-anaotion-processor-analysis/","summary":"\u003cp\u003e上一篇介绍了,\u003ccode\u003eSwoftApplication\u003c/code\u003e里定义了6个Processor对象。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function processors(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EnvProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConfigProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new AnnotationProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new BeanProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EventProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConsoleProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e所有的Processor实现都在\u003ccode\u003eframework\\src\\Processor\u003c/code\u003e目录下。\u003c/p\u003e","content_html":"\u003cp\u003e上一篇介绍了,\u003ccode\u003eSwoftApplication\u003c/code\u003e里定义了6个Processor对象。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprotected function processors(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EnvProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConfigProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new AnnotationProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new BeanProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EventProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConsoleProcessor($this),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e所有的Processor实现都在\u003ccode\u003eframework\\src\\Processor\u003c/code\u003e目录下。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eEnvProcessor,运行环境检查。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eConfigProcessor,配置相关。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eAnnotationProcessor,注解相关。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eBeanProcessor,Bean相关。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eEventProcessor,事件相关。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eConsoleProcessor,命令行输入相关。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e今天先讲一下\u003ccode\u003eAnnotationProcessor\u003c/code\u003e这个模块的实现。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Processor;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Exception;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Annotation\\AnnotationRegister;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Log\\Helper\\CLog;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Annotation processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e AnnotationProcessor \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e Processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows Exception\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeAnnotation()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::warning(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Stop annotation processor by beforeAnnotation return false\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e false;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eapp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Find AutoLoader classes\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e Parse \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e collect annotations\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::load([\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;inPhar\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \\IN_PHAR,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;basePath\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eapp\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetBasePath(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;notifyHandler\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e [\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;notifyHandler\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;disabledAutoLoaders\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eapp\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDisabledAutoLoaders(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;disabledPsr4Prefixes\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eapp\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDisabledPsr4Prefixes(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e AnnotationRegister::getClassStats();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Annotations is scanned(autoloader \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e, annotation \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e, parser \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%d\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e)\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;autoloader\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;annotation\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003estats[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;parser\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e );\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterAnnotation();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esee \\Swoft\\Annotation\\\u003cspan style=\"color:#a6e22e\"\u003eResource\u003c/span\u003e\\AnnotationResource::load()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function notifyHandler(string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eswitch\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etype) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;excludeNs\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Exclude namespace \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noLoaderFile\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;No autoloader on \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noLoaderClass\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Autoloader class not exist \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;findLoaderClass\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Find autoloader \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;addLoaderClass\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Parse autoloader \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ecase\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noExistClass\u0026#39;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::debug(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Skip interface or trait \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003etarget);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ebreak\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e核心逻辑调用\u003ccode\u003eAnnotationRegister\u003c/code\u003e类的\u003ccode\u003eload\u003c/code\u003e方法,定义如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Load annotation \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e function load(array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresource \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new AnnotationResource(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eresource\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eload();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里又调用了\u003ccode\u003eAnnotationResource\u003c/code\u003e类的\u003ccode\u003eload\u003c/code\u003e方法,定义如下。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Load annotation resource by find ClassLoader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic function load(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprefixDirsPsr4 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclassLoader\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPrefixesPsr4();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprefixDirsPsr4 as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epaths) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Only scan namespaces\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eonlyNamespaces \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003ein_array(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eonlyNamespaces, true)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;excludeNs\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e It is excluded psr4 prefix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisExcludedPsr4Prefix(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::registerExcludeNs(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;excludeNs\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Find package\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003ecomponent loader \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epaths as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAnnotationClassLoaderFile(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003efile_exists(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noLoaderFile\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclearBasePath(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath), \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetAnnotationLoaderClassName(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eclass_exists(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noLoaderClass\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderObject \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eloaderObject instanceof LoaderInterface) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;invalidLoader\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;findLoaderClass\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclearBasePath(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e If is disable, will skip scan annotation classes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eisset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edisabledAutoLoaders[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::registerAutoLoaderFile(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderFile);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;addLoaderClass\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eloadAnnotation(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderObject);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Storage auto loader to register\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::addAutoLoader(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloaderObject);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过\u003ccode\u003egetPrefixesPsr4\u003c/code\u003e方法获取所有自动加载的命名空间和目录,遍历目录下的\u003ccode\u003eAutoLoader.php\u003c/code\u003e文件。\u003c/p\u003e\n\u003cp\u003e通过\u003ccode\u003eregisterAutoLoaderFile\u003c/code\u003e注册自动加载文件到\u003ccode\u003eAnnotationRegister\u003c/code\u003e对象上。\u003c/p\u003e\n\u003cp\u003e然后调用了\u003ccode\u003eloadAnnotation\u003c/code\u003e方法,传入的是一个\u003ccode\u003eautoload\u003c/code\u003e对象。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Load annotations from an component loader config\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam LoaderInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003ethrows ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function loadAnnotation(LoaderInterface \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloader): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ensPaths \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloader\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPrefixDirs();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ensPaths as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eiterator \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e DirectoryHelper::recursiveIterator(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e SplFileInfo \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eiterator as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilePath \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetPathname();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eisDir();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_dir(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilePath)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efileName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetFilename();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eextension \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esplFileInfo\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetExtension();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eloaderClassSuffix \u003cspan style=\"color:#f92672\"\u003e!==\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eextension \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e strpos(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efileName, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.\u0026#39;\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e It is exclude filename\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eexcludedFilenames[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efileName])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::registerExcludeFilename(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efileName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esuffix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;.\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eloaderClassSuffix);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epathName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e str_replace([\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epath, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003esuffix], [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\\\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e], \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilePath);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e sprintf(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003epathName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Fix repeat included file bug\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eautoload \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e in_array(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003efilePath, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eincludedFiles, true);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Will filtering: interfaces \u003cspan style=\"color:#f92672\"\u003eand\u003c/span\u003e traits\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003eclass_exists(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, \u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003eautoload)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003enotify(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;noExistClass\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Parse annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eparseAnnotation(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ens, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过getPrefixDirs获取当前命名空间的目录,然后通过\u003ccode\u003erecursiveIterator\u003c/code\u003e遍历目录下的文件。\u003c/p\u003e\n\u003cp\u003e排除目录和非\u003ccode\u003e.php\u003c/code\u003e结尾的文件,最后会调用\u003ccode\u003eparseAnnotation\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Parser annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $namespace\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $className\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseAnnotation(string $namespace, string $className): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Annotation reader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $reflectionClass = new ReflectionClass($className);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Fix ignore abstract\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($reflectionClass-\u0026gt;isAbstract()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation = $this-\u0026gt;parseOneClassAnnotation($reflectionClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($oneClassAnnotation)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里调用了\u003ccode\u003eparseOneClassAnnotation\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * Parse an class annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param ReflectionClass $reflectionClass\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e *\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @return array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws AnnotationException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @throws ReflectionException\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprivate function parseOneClassAnnotation(ReflectionClass $reflectionClass): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Annotation reader\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $reader = new AnnotationReader();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $className = $reflectionClass-\u0026gt;getName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation = [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $classAnnotations = $reader-\u0026gt;getClassAnnotations($reflectionClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Register annotation parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($classAnnotations as $classAnnotation) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($classAnnotation instanceof AnnotationParser) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;registerParser($className, $classAnnotation);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Class annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($classAnnotations)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;annotation\u0026#39;] = $classAnnotations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;reflection\u0026#39;] = $reflectionClass;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Property annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $reflectionProperties = $reflectionClass-\u0026gt;getProperties();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($reflectionProperties as $reflectionProperty) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $propertyName = $reflectionProperty-\u0026gt;getName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $propertyAnnotations = $reader-\u0026gt;getPropertyAnnotations($reflectionProperty);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($propertyAnnotations)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;properties\u0026#39;][$propertyName][\u0026#39;annotation\u0026#39;] = $propertyAnnotations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;properties\u0026#39;][$propertyName][\u0026#39;reflection\u0026#39;] = $reflectionProperty;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e // Method annotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $reflectionMethods = $reflectionClass-\u0026gt;getMethods();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach ($reflectionMethods as $reflectionMethod) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $methodName = $reflectionMethod-\u0026gt;getName();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $methodAnnotations = $reader-\u0026gt;getMethodAnnotations($reflectionMethod);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($methodAnnotations)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;methods\u0026#39;][$methodName][\u0026#39;annotation\u0026#39;] = $methodAnnotations;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;methods\u0026#39;][$methodName][\u0026#39;reflection\u0026#39;] = $reflectionMethod;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $parentReflectionClass = $reflectionClass-\u0026gt;getParentClass();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($parentReflectionClass !== false) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $parentClassAnnotation = $this-\u0026gt;parseOneClassAnnotation($parentReflectionClass);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if (!empty($parentClassAnnotation)) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $oneClassAnnotation[\u0026#39;parent\u0026#39;] = $parentClassAnnotation;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return $oneClassAnnotation;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里就是解析注解了,可以看到分别有类注解、属性注解和方法注解三类。\u003c/p\u003e\n\u003cp\u003e这里注意这一段代码。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Register annotation parser\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eforeach ($classAnnotations as $classAnnotation) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e if ($classAnnotation instanceof AnnotationParser) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e $this-\u0026gt;registerParser($className, $classAnnotation);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e return [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e遍历注解类,如果注解属于\u003ccode\u003eAnnotationParser\u003c/code\u003e实例,这里调用\u003ccode\u003eregisterParser\u003c/code\u003e进行注册。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $annotationClass\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e * @param string $parserClassName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e */\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic static function registerParser(string $annotationClass, string $parserClassName): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$classStats[\u0026#39;parser\u0026#39;]++;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::$parsers[$annotationClass] = $parserClassName;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e回到上一个方法,解析完后,又调用了\u003ccode\u003eAnnotationRegister\u003c/code\u003e类的\u003ccode\u003eregisterAnnotation\u003c/code\u003e方法进行注册。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloadNamespace\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassAnnotation\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epublic \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e function registerAnnotation(string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloadNamespace, string \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName, array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassAnnotation): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassStats[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;annotation\u0026#39;\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e self::\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eannotations[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eloadNamespace][\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassName] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eclassAnnotation;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e至此,整个\u003ccode\u003eAnnotationProcessor\u003c/code\u003e加载完毕,这里\u003ccode\u003eAnnotationRegister\u003c/code\u003e类里会有\u003ccode\u003eannotations\u003c/code\u003e和\u003ccode\u003eparsers\u003c/code\u003e两个属性,这个信息在后面的\u003ccode\u003eBeanProcessor\u003c/code\u003e里还会用到。\u003c/p\u003e\n","date_published":"2019-08-29T19:11:04+08:00","tags":["Swoft"]},{"title":"Swoft 框架运行分析(一)","id":"https://liudon.com/posts/swoft-execution-analysis/","url":"https://liudon.com/posts/swoft-execution-analysis/","summary":"\u003cblockquote\u003e\n\u003cp\u003eSwoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。\u003c/p\u003e\n\u003cp\u003e刚开始看的时候,感觉自己像个原始人,完全看不懂。\u003c/p\u003e","content_html":"\u003cblockquote\u003e\n\u003cp\u003eSwoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。\u003c/p\u003e\n\u003cp\u003e刚开始看的时候,感觉自己像个原始人,完全看不懂。\u003c/p\u003e\n\u003cp\u003e官方文档没有介绍Swoft的实现,网上的一些文章跟当前版本代码已经不一致了。\u003c/p\u003e\n\u003cp\u003e自己花了一周时间,终于梳理清楚了,看完更觉得自己是个原始人了。\u003c/p\u003e\n\u003cp\u003e使用的框架组件版本为:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eswoft-2.0.5\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eswoft-component-2.0.5\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里以Swoft启动http server为例。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ephp bin/swoft http:start\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e执行上述命令,启动http server。\u003c/p\u003e\n\u003cp\u003e这里执行的是bin/swoft文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e#!/usr/bin/env php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;?php declare(strict_types=1);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Bootstrap\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erequire_once __DIR__ . \u0026#39;/bootstrap.php\u0026#39;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSwoole\\Coroutine::set([\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u0026#39;max_coroutine\u0026#39; =\u0026gt; 300000,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e// Run application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e(new \\App\\Application())-\u0026gt;run();\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里引入\u003ccode\u003ebootstrap.php\u003c/code\u003e文件,引入composer自动加载文件。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Composer autoload\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erequire_once dirname(__DIR__) \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/vendor/autoload.php\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后执行\u003ccode\u003eSwoft\\App\\Application\u003c/code\u003e类下的\u003ccode\u003erun\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp declare(strict_types\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace App;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\SwoftApplication;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function date_default_timezone_set;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class Application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e Application \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e SwoftApplication\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e protected function beforeInit(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e parent::beforeInit();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e date_default_timezone_set(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Asia/Shanghai\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里继承了\u003ccode\u003eSwoft\\SwoftApplication\u003c/code\u003e类,这里只粘贴了部分代码。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Swoft application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e SwoftApplication implements SwoftInterface, ApplicationInterface\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Class constructor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function __construct(array \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Check runtime env\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e SwoftHelper::checkRuntime();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Storage as global \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e property\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Swoft::\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eapp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Before init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeInit();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Init console logger\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einitCLogger();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Can setting properties by array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ObjectHelper::init(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003econfig);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Init application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003einit();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CLog::info(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Project path is \u0026lt;info\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e%s\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026lt;/info\u0026gt;\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebasePath);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e After init\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eafterInit();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e protected function init(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e Init system path aliases\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003efindBasePath();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esetSystemAlias();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessors \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessors();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e new ApplicationProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessor\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eaddFirstProcessor(\u003cspan style=\"color:#f92672\"\u003e...$\u003c/span\u003eprocessors);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Run application\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function run(): void\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ebeforeRun()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessor\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehandle();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e ProcessorInterface[]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e protected function processors(): array\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EnvProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConfigProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new AnnotationProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new BeanProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new EventProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e new ConsoleProcessor(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e__construct\u003c/code\u003e方法里检查运行环境,初始化日志组件,然后调用了\u003ccode\u003einit\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003einit\u003c/code\u003e方法里声明了\u003ccode\u003eprocessor\u003c/code\u003e对象。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eprocessors\u003c/code\u003e方法定义了Swoft框架的6个Processor对象。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003erun\u003c/code\u003e方法里直接调用\u003ccode\u003eprocessor\u003c/code\u003e对象的\u003ccode\u003ehandler\u003c/code\u003e方法。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-gdscript3\" data-lang=\"gdscript3\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e?\u003c/span\u003ephp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enamespace Swoft\\Processor;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse Swoft\\Stdlib\\Helper\\ArrayHelper;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003euse function get_class;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Application processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003esince \u003cspan style=\"color:#ae81ff\"\u003e2.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e ApplicationProcessor \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e Processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e ProcessorInterface[]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e private \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessors \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Handle application processors\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function handle(): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edisabled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eapplication\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetDisabledProcessors();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e foreach (\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessors as \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e get_class(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e//\u003c/span\u003e If is disabled, skip handle\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (isset(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003edisabled[\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e])) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003ehandle();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e true;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Add first processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam Processor[] \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function addFirstProcessor(Processor \u003cspan style=\"color:#f92672\"\u003e...$\u003c/span\u003eprocessor): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e array_unshift(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessors, \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e true;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Add last processor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam Processor[] \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function addLastProcessor(Processor \u003cspan style=\"color:#f92672\"\u003e...$\u003c/span\u003eprocessor): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e array_push(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessors, \u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessor);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e true;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e/**\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e Add processors\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam \u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eindex\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003eparam Processor[] \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eprocessors\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e@\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e public function addProcessor(\u003cspan style=\"color:#a6e22e\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eindex, Processor \u003cspan style=\"color:#f92672\"\u003e...$\u003c/span\u003eprocessors): \u003cspan style=\"color:#a6e22e\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ArrayHelper::insert(\u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003ethis\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eprocessors, \u003cspan style=\"color:#f92672\"\u003e$\u003c/span\u003eindex, \u003cspan style=\"color:#f92672\"\u003e...$\u003c/span\u003eprocessors);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e true;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eaddFirstProcessor\u003c/code\u003e方法把process对象赋值给\u003ccode\u003e$this-\u0026gt;processors\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ehandle\u003c/code\u003e方法遍历\u003ccode\u003eprocessors\u003c/code\u003e对象,循环执行\u003ccode\u003ehandle\u003c/code\u003e方法。\u003c/p\u003e\n\u003cp\u003eSwoft的核心逻辑都是靠上面定义的6个Processor模块实现的,接下来一个一个分析。\u003c/p\u003e\n","date_published":"2019-08-29T17:22:28+08:00","tags":["Swoft"]},{"title":"BCMath 与 科学计数","id":"https://liudon.com/posts/bcmath-and-exponential-notation/","url":"https://liudon.com/posts/bcmath-and-exponential-notation/","summary":"\u003cp\u003e代码如下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;?php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho 9.99997600 + 2.4E-5;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u0026#34;\\n===\\n\u0026#34;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho bcadd(9.99997600, 2.4E-5, 8);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e结果为\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e10\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e===\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e9.99997600\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e问了朋友,查了各种资料,终于在PHP手册里发现了这段话。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eCaution\nPassing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).\u003c/p\u003e","content_html":"\u003cp\u003e代码如下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;?php\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho 9.99997600 + 2.4E-5;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho \u0026#34;\\n===\\n\u0026#34;;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho bcadd(9.99997600, 2.4E-5, 8);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e结果为\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e10\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e===\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e9.99997600\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e问了朋友,查了各种资料,终于在PHP手册里发现了这段话。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eCaution\nPassing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003ePHP的BCMath方法不支持科学计数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e解决方法:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eecho bcadd(9.99997600, number_format(2.4E-5, 8, \u0026#39;.\u0026#39;, \u0026#39;\u0026#39;), 8);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003ePHP里浮点数相关的运算一定要使用BCMath函数!\u003c/strong\u003e\u003c/p\u003e\n","date_published":"2019-08-16T19:34:34+08:00","tags":[]},{"title":"Flink Could Not Resolve Resourcemanager Address","id":"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/","url":"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/","summary":"\u003cp\u003e什么是Flink。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eApache Flink® - Stateful Computations over Data Streams\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eFlink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。\u003c/p\u003e","content_html":"\u003cp\u003e什么是Flink。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eApache Flink® - Stateful Computations over Data Streams\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eFlink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。\u003c/p\u003e\n\u003cp\u003e这里使用\u003ccode\u003e单机模式\u003c/code\u003e。\u003c/p\u003e\n\u003ch4 id=\"问题表现\"\u003e问题表现\u003c/h4\u003e\n\u003cp\u003e启动Flink\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_80_180_centos /usr/local/flink-1.7.2]# ./bin/start-cluster.sh \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStarting cluster.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStarting standalonesession daemon on host VM_80_180_centos.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eStarting taskexecutor daemon on host VM_80_180_centos.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e查看进程\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_80_180_centos /usr/local/flink-1.7.2]# jps\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e10442 StandaloneSessionClusterEntrypoint\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e11067 Jps\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e10909 TaskManagerRunner\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_80_180_centos /usr/local/flink-1.7.2]# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e查看日志,发现\u0026quot;Could not resolve ResourceManager address\u0026quot;的错误。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_80_180_centos /usr/local/flink-1.7.2]# tail -f log/flink-root-taskexecutor-*.log\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2019-03-27 19:43:23,804 INFO org.apache.flink.runtime.taskexecutor.TaskExecutor - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type \u0026#34;akka.actor.Identify\u0026#34;..\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e2019-03-27 19:43:43,843 INFO org.apache.flink.runtime.taskexecutor.TaskExecutor - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type \u0026#34;akka.actor.Identify\u0026#34;..\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e访问Flink的web页面,发现task数全为0.\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu17946149840019200338.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu11889884118258270666.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"63c9befaly1g1igknr8lyj21ae09874j.jpg\" width=\"1670\" height=\"332\" alt=\"flink no task\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003ch4 id=\"问题原因\"\u003e问题原因:\u003c/h4\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu15031826398138036045.webp 899w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu18129525753661784040.jpg 899w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"63c9befaly1g1igoreyi9j20oz04mdg7.jpg\" width=\"899\" height=\"166\" alt=\"taskmanager.host\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003eFlink的taskmanager.host默认为空,会使用hostname。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e[root@VM_80_180_centos /usr/local/flink-1.7.2]# ping VM_80_180_centos\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePING VM_80_180_centos (100.125.80.180) 56(84) bytes of data.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=1 ttl=64 time=0.022 ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=2 ttl=64 time=0.038 ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=3 ttl=64 time=0.038 ms\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eFlink的jobmanager.host默认为localhost。\u003c/p\u003e\n\u003cp\u003e这里jobmanager和taskmanager绑定的ip不一样,导致出错。\u003c/p\u003e\n\u003ch4 id=\"解决办法\"\u003e解决办法:\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003evim conf/flink-conf.yaml\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e添加下面这行配置\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etaskmanager.host: localhost\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e保存退出,然后重新启动Flink,这个时候在web端就可以看到有可用task了。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu412242710392457856.webp 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu9144468641138631652.jpg 1080w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"63c9befaly1g1ih665ffjj21a709374r.jpg\" width=\"1663\" height=\"327\" alt=\"flink web\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n","date_published":"2019-03-28T13:00:50+08:00","tags":["Flink"]},{"title":"解决Sublime Text安装包时\"There Are No Packages Available for Installation\"的报错","id":"https://liudon.com/posts/there-are-no-packages-available-for-installation/","url":"https://liudon.com/posts/there-are-no-packages-available-for-installation/","summary":"\u003cp\u003e今天安装hugofy的包时,一直遇到\u0026quot;There Are No Packages Available for Installation\u0026quot;的错误。\n按网上的教程,配置host,配置代理都不起作用。\u003c/p\u003e","content_html":"\u003cp\u003e今天安装hugofy的包时,一直遇到\u0026quot;There Are No Packages Available for Installation\u0026quot;的错误。\n按网上的教程,配置host,配置代理都不起作用。\u003c/p\u003e\n\u003cp\u003e本机确定是可以访问\u003ca href=\"https://packagecontrol.io/channel_v3.json\"\u003ehttps://packagecontrol.io/channel_v3.json\u003c/a\u003e这个地址的。\u003c/p\u003e\n\u003cp\u003e然后按教程把这文件放到本地,配置channel指向本地这个文件,然后提示json解析失败。\n然后检查这个文件,发现文件好像不全。然后换到其他机器curl这个地址,发现下载下来的文件确实不全,不是合法的json内容。\u003c/p\u003e\n\u003cp\u003e\n\n\u003cpicture\u003e\u003csource type=\"image/webp\" srcset=\"https://liudon.com/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu541995141045692674.webp 556w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003csource type=\"image/jpeg\" srcset=\"https://liudon.com/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu18275729127856576344.jpg 556w\" sizes=\"(min-width: 768px) 1080px, 100vw\" /\u003e\u003cimg src=\"63c9befaly1fz7anr0smxj20fg0famzv.jpg\" width=\"556\" height=\"550\" alt=\"下载文件内容截图\" title=\"\" loading=\"lazy\" /\u003e\n\t\t\u003c/picture\u003e\n\n\u003c/p\u003e\n\u003cp\u003e又搜索一番后,找到一个case。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/wbond/package_control/issues/1397\"\u003ePackage Control: There are no packages available for installation/Server Error\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e原来是官方的文件下载出问题了,可以先按上面链接里的方法修改,验证可行。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eMeanwhile, you can add\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026#34;channels\u0026#34;: [ \u0026#34;https://erhan.in/channel_v3.json\u0026#34; ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eto Preferences \u0026gt; Package Settings \u0026gt; Package Control \u0026gt; Settings - User file.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eThis is the latest snapshot of the original JSON file from web.archive.org.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","date_published":"2019-01-11T17:13:14+08:00","tags":[]},{"title":"关于本站","id":"https://liudon.com/about/","url":"https://liudon.com/about/","summary":"\u003cp\u003e大家好,欢迎访问我的博客。\u003c/p\u003e\n\u003ch4 id=\"建站缘由\"\u003e建站缘由\u003c/h4\u003e\n\u003cp\u003e大学期间,被人忽悠报了一个计算机培训班,误打误撞的进入了互联网行业。\u003c/p\u003e\n\u003cp\u003e11年在网友的帮助下,用wordPress搭建了\u003ca href=\"https://www.liudon.org/1.html\"\u003e博客\u003c/a\u003e,随即开始了我的博主生涯。\u003c/p\u003e","content_html":"\u003cp\u003e大家好,欢迎访问我的博客。\u003c/p\u003e\n\u003ch4 id=\"建站缘由\"\u003e建站缘由\u003c/h4\u003e\n\u003cp\u003e大学期间,被人忽悠报了一个计算机培训班,误打误撞的进入了互联网行业。\u003c/p\u003e\n\u003cp\u003e11年在网友的帮助下,用wordPress搭建了\u003ca href=\"https://www.liudon.org/1.html\"\u003e博客\u003c/a\u003e,随即开始了我的博主生涯。\u003c/p\u003e\n\u003cp\u003e最开始的域名是\u003ccode\u003eliudon.org\u003c/code\u003e,取自本人名字的拼音,14年的时候经历了一次\u003ca href=\"https://www.liudon.org/1255.html\"\u003e域名被盗\u003c/a\u003e,好在最后找回了。\u003c/p\u003e\n\u003cp\u003e中间又申请了\u003ccode\u003eliudon.xyz\u003c/code\u003e域名,一直垂涎于\u003ccode\u003eliudon.com\u003c/code\u003e,终于在22年被我拿到手了,\u003ca href=\"https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/\"\u003e十一年的等待\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e本站使用\u003ccode\u003ehugo\u003c/code\u003e生成,存储在\u003ccode\u003eGithub\u003c/code\u003e上,通过\u003ca href=\"https://liudon.com/posts/deploy-blog-to-cloudflare-pages/\"\u003eCloudflare Pages\u003c/a\u003e和\u003ca href=\"https://liudon.com/posts/deploy-blog-to-ipfs/\"\u003eIPFS\u003c/a\u003e部署访问。\u003c/p\u003e\n\u003ch4 id=\"关于本人\"\u003e关于本人\u003c/h4\u003e\n\u003cp\u003e喜欢做梦、已婚的80后男生。\u003c/p\u003e\n\u003cp\u003e如果您有意见或建议,欢迎您通过\u003ccode\u003ei@liudon.org\u003c/code\u003e联系我。\u003c/p\u003e\n\u003ch4 id=\"朋友们\"\u003e朋友们\u003c/h4\u003e\n\u003ca target=\"_blank\" href=https://dvel.me title=Dvel’s\u0026#32;Blog class=\"friendurl\"\u003e\r\n \u003cdiv class=\"frienddiv\"\u003e\r\n \u003cdiv class=\"frienddivleft\"\u003e\r\n \u003cimg class=\"myfriend\" src=https://dvel.me/avatar.png /\u003e\r\n \u003c/div\u003e\r\n \u003cdiv class=\"frienddivright\"\u003e\r\n \u003cdiv class=\"friendname\"\u003eDvel’s Blog\u003c/div\u003e\r\n \u003cdiv class=\"friendinfo\"\u003eLess is More.\u003c/div\u003e\r\n \u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=https://immmmm.com/ title=林木木木木木 class=\"friendurl\"\u003e\r\n \u003cdiv class=\"frienddiv\"\u003e\r\n \u003cdiv class=\"frienddivleft\"\u003e\r\n \u003cimg class=\"myfriend\" src=https://cdn.sep.cc/avatar/ba83fa02fc4b2ba621514941307e21be?d\u0026#61;identicon /\u003e\r\n \u003c/div\u003e\r\n \u003cdiv class=\"frienddivright\"\u003e\r\n \u003cdiv class=\"friendname\"\u003e林木木木木木\u003c/div\u003e\r\n \u003cdiv class=\"friendinfo\"\u003e木木木木木\u003c/div\u003e\r\n \u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=https://taoshu.in/ title=涛叔 class=\"friendurl\"\u003e\r\n \u003cdiv class=\"frienddiv\"\u003e\r\n \u003cdiv class=\"frienddivleft\"\u003e\r\n \u003cimg class=\"myfriend\" src=https://taoshu.in/avatar.jpg /\u003e\r\n \u003c/div\u003e\r\n \u003cdiv class=\"frienddivright\"\u003e\r\n \u003cdiv class=\"friendname\"\u003e涛叔\u003c/div\u003e\r\n \u003cdiv class=\"friendinfo\"\u003e涛叔\u003c/div\u003e\r\n \u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/a\u003e\n\u003ca target=\"_blank\" href=https://laozhang.org/ title=老张博客 class=\"friendurl\"\u003e\r\n \u003cdiv class=\"frienddiv\"\u003e\r\n \u003cdiv class=\"frienddivleft\"\u003e\r\n \u003cimg class=\"myfriend\" src=https://picx.s3.bitiful.net/2023/05/01/644f0e3acd666.png /\u003e\r\n \u003c/div\u003e\r\n \u003cdiv class=\"frienddivright\"\u003e\r\n \u003cdiv class=\"friendname\"\u003e老张博客\u003c/div\u003e\r\n \u003cdiv class=\"friendinfo\"\u003e老张博客\u003c/div\u003e\r\n \u003c/div\u003e\r\n \u003c/div\u003e\r\n\u003c/a\u003e\n","date_published":"2019-01-10T14:11:09+08:00","tags":[]},{"title":"2019,新开始","id":"https://liudon.com/posts/the-first-post/","url":"https://liudon.com/posts/the-first-post/","summary":"\u003cp\u003e从2011年开始写博客,博客程序从WordPress换成Typecho。\n早就有想法换成静态博客,一直没时间搞。\u003c/p\u003e\n\u003cp\u003e2019年了,新年新气象,用hugo + github pages搞了个新博客。\u003c/p\u003e","content_html":"\u003cp\u003e从2011年开始写博客,博客程序从WordPress换成Typecho。\n早就有想法换成静态博客,一直没时间搞。\u003c/p\u003e\n\u003cp\u003e2019年了,新年新气象,用hugo + github pages搞了个新博客。\u003c/p\u003e\n\u003cp\u003e具体部署过程参考文章:\n\u003ca href=\"https://axdlog.com/zh/2018/using-hugo-and-travis-ci-to-deploy-blog-to-github-pages-automatically/\"\u003e利用Travis CI和Hugo將Blog自動部署到Github Pages\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e这篇文章就是通过这种方式来更新的,感觉很是神奇。\n再也不用关注服务器性能这些东西了,只需要专心写字就好了。\u003c/p\u003e\n\u003cp\u003e\u003cdel\u003e接下来只需要搞定自定义域名了,域名还在认证中,无法做解析。\u003c/del\u003e\u003c/p\u003e\n\u003cp\u003e自定义域名也搞定了,以后就可以正式切到新博客了。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://liudon.org\"\u003e老博客\u003c/a\u003e只做备份了,不再更新了。\u003c/p\u003e\n","date_published":"2019-01-09T15:17:04+08:00","tags":[]},{"title":"Search","id":"https://liudon.com/search/","url":"https://liudon.com/search/","summary":"","content_html":"","date_published":"0001-01-01T00:00:00Z","tags":[]}]} \ No newline at end of file diff --git a/index.xml b/index.xml new file mode 100644 index 000000000..e72190529 --- /dev/null +++ b/index.xml @@ -0,0 +1,8995 @@ + + + + 流动 + https://liudon.com/ + Recent content on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 24 Sep 2024 21:30:21 +0800 + + + Github Pages 部署流程解析 + https://liudon.com/posts/github-pages-deployment-tutorial/ + Tue, 24 Sep 2024 21:30:21 +0800 + https://liudon.com/posts/github-pages-deployment-tutorial/ + <p>上周末在<a href="https://liudon.com/posts/building-a-workout-page/">搭建个人锻炼页面</a>时,遇到个<code>Github Pages</code>部署的困惑。</p> +<p>看了<code>running_page</code>项目文档,是支持部署到<code>Github Pages</code>页面的,对应的操作流程定义在<a href="https://github.com/yihong0618/running_page/blob/master/.github/workflows/gh-pages.yml">github/workflows/gh-pages.yml</a>文件。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Install dependencies +</span></span><span style="display:flex;"><span> run: pnpm install +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Build +</span></span><span style="display:flex;"><span> run: PATH_PREFIX<span style="color:#f92672">=/$</span>{{ github<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>repository<span style="color:#f92672">.</span>name }} pnpm build +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Upload artifact +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>upload<span style="color:#f92672">-</span>pages<span style="color:#f92672">-</span>artifact<span style="color:#960050;background-color:#1e0010">@</span>v3 +</span></span><span style="display:flex;"><span> with: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Upload dist repository</span> +</span></span><span style="display:flex;"><span> path: <span style="color:#e6db74">&#39;./dist&#39;</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Deploy to GitHub Pages +</span></span><span style="display:flex;"><span> id: deployment +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>deploy<span style="color:#f92672">-</span>pages<span style="color:#960050;background-color:#1e0010">@</span>v4 +</span></span></code></pre></div><p>核心逻辑就是上面这段。</p> + 上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。

      +

      看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。

      +
          - name: Install dependencies
      +    run: pnpm install
      +    - name: Build
      +    run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build
      +
      +    - name: Upload artifact
      +    uses: actions/upload-pages-artifact@v3
      +    with:
      +        # Upload dist repository
      +        path: './dist'
      +    - name: Deploy to GitHub Pages
      +    id: deployment
      +    uses: actions/deploy-pages@v4
      +

      核心逻辑就是上面这段。

      +

      之前搞过部署hugo静态站点到Github Pages,使用的分支方式部署,编译后的静态文件单独用一个分支存放。

      +

      这里以我自己的博客项目举例,大致流程如下图:

      +

      + +github-pages-deploy-flow + + +

      +

      按我的理解,这里最终访问的文件内容是存在gh-page分支下的。

      +

      但是实际部署完running_page项目后,我发现并没有出现gh-page分支,但是Github Pages却可以正常访问。

      +

      有点不可思议,这个访问的数据是在哪里的呢?

      +

      带着这个疑问,在v2ex上发了个咨询贴

      +

      经过网友解惑,大致搞明白了这里的流程:

      +

      + +github-pages-deploy-flow + + +

      +

      Github Pages的发布源有两种方式,通过分支部署和通过Github Actions部署,分别对应上图的两条分支。

      +

      这里最终都会将build后的静态文件部署到Github Pages服务上,供用户访问。

      +

      分支部署的方式,其实是有一个内置工作流部署到Github Pages服务上的。

      +

      + + + + +

      +

      整个部署流程大致就是这样,最终的静态文件都是存在Github Pages服务上的。

      +]]>
      +
      + + 搭建个人锻炼页面 + https://liudon.com/posts/building-a-workout-page/ + Sun, 22 Sep 2024 16:57:38 +0800 + https://liudon.com/posts/building-a-workout-page/ + <p>工作的缘故,平时基本一坐一天,缺少运动。</p> +<p>时间久了,各种毛病也就出来了。</p> +<p>搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。</p> +<p>坚持了一段时间,也不了了之了。</p> + 工作的缘故,平时基本一坐一天,缺少运动。

      +

      时间久了,各种毛病也就出来了。

      +

      搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。

      +

      坚持了一段时间,也不了了之了。

      +

      今年开始,决定骑车通勤,上下班路上运动一下。

      +

      最近在别人博客里发现了运动记录,发现是通过running_page项目实现的。

      +

      顺藤摸瓜,又发现了workouts_page项目,支持多种运动。

      +

      于是看文档,部署起来,我的个人锻炼页面

      +

      + +workout page + + +

      +

      整个流程:

      +

      使用Apple Watch记录运动,导入到Strava应用里,在通过workouts_page工作流拉取数据构建页面。

      +

      部署过程中,顺带发现个问题,提了个PR。

      +

      这里还有个小插曲,没搞明白PR的流程,在未合入前又提交了其他代码,只好重新提了一个PR。😂

      +]]>
      +
      + + 你好 Follow + https://liudon.com/posts/hi-follow/ + Tue, 17 Sep 2024 00:53:38 +0800 + https://liudon.com/posts/hi-follow/ + <blockquote> +<p>Follow: Next generation information browser.</p> +</blockquote> +<p>最近博客圈开始流行<code>Follow邀请码</code>,大家各种求码,一码难求。</p> +<p>蹲在<code>Discord</code>群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。</p> + +

      Follow: Next generation information browser.

      + +

      最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。

      +

      蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。

      +

      上周五好不容易加上管理员,要到了一枚邀请码,终于可以激活体验了。

      +

      Follow里,订阅变的异常简单,输入url,它会自己检查rss订阅。

      +

      + +Follow + + +

      +

      另外发现我的博客,在Follow显示的内容不全。

      +

      检查了一下,发现是输出的RSS内容不全。

      +

      修改hugo配置文件,开启RSS输出全文。

      +
      ShowFullTextinRSS: true
      +

      从木木大佬那里看到,可以认证自己的Feed,我也来搞一下我的。

      +
      This message is used to verify that this feed (feedId:55815884011044914) belongs to me (userId:56204227179125760). 
      +Join me in enjoying the next generation information browser https://follow.is.
      +

      咱也是带标的了。

      +

      + +认证 + + +

      +]]>
      +
      + + 中秋爬山 + https://liudon.com/posts/mid-autumn-festival-climb/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/posts/mid-autumn-festival-climb/ + <p>中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。</p> +<p>晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。</p> + 中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。

      +

      晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。

      +

      园博园开了灯会,微博上看说是人巨多,还是放弃吧。

      +

      跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。

      +

      第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。

      +

      还好不远,山也不高,我们也不着急,就当遛弯。

      +

      来了好几次了,进园就直奔主题:爬。

      +

      这次我俩先走了一段山路,虽然是台阶,但是确实快。

      +

      昨天玩的太累,我们商量着还是继续走坡道吧。

      +

      花了40分钟左右登顶,最快的一次记录了。

      +

      + +登顶 + + +

      +

      爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。

      +

      + +小憩 + + +

      +

      今天天气一般,能见度不高,远处都是灰蒙蒙的。

      +

      + + + + +

      +

      歇到1点多,我俩开始下山。

      +

      之前在微博看到陈晓卿分享的一家新疆馆子白钻美食,决定晚上带娃去尝尝。

      +

      坐了1个半小时的地铁,到了吕营大街。

      +

      高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。

      +

      我俩傻乎乎的爬楼上来,累个够呛。

      +

      建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。

      +

      4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。

      +

      馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂

      +

      不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。

      +

      + +羊腿抓饭 + + +

      +

      + +羊肉串 + + +

      +

      然后又点了一份过油拌面,面条非常劲道,味道非常棒。

      +

      + +过油拌面 + + +

      +

      喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。

      +

      + +砖茶 + + +

      +

      店不大,但是味道挺好,推荐去试试,就是有点远。

      +

      + +白钻美食 + + +

      +

      吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。

      +

      今天是暴走的一天。

      +

      + +健身记录 + + +

      +]]>
      +
      + + Google Adsense的审核之旅 + https://liudon.com/posts/my-google-adsense-approval-journey/ + Mon, 16 Sep 2024 23:18:50 +0800 + https://liudon.com/posts/my-google-adsense-approval-journey/ + <p>中午的时候,突然收到一条消息,打开一看,提示我的<code>Google Adsense</code>审核通过了。</p> +<p>偶然发现<code>Google Adsense</code>里居然有40美金,想起来是之前<a href="https://liudon.org">老博客</a>加的广告。</p> + 中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。

      +

      偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。

      +

      看着新博客每天也有了一些访问,打算申请Google Adsense,补充些维护成本。

      +

      按之前的流程搞了一遍,提交了申请。

      +

      结果过了1周多,收到审核不通过邮件,说是不符合规范:低质内容,质量不高。

      +

      搜了一下,说是现在新网站审核门槛高了。

      +

      不放弃,继续申请呗。

      +

      + +approval google adsense + + +

      +

      从3月份开始,申请了7次,全部被拒。

      +

      尤其是8月25日被拒后,提示我审核次数过多,必须得等到8月31日以后才能再次申请。

      +

      上社区发了帖子,咨询到底是什么原因,结果也没收到答复。

      +

      这个时候,就已经有点心灰意冷,想要放弃了。

      +

      9月5日的时候,想着再最后申请一把试试看,再不通过就算了。

      +

      等了1周多,感觉这次估计又悬了,已经放弃了,结果今天竟然审核通过了。

      +

      历经了8次申请,耗时半年,终于申请下来了,算是这段时间难得的一件好事。

      +]]>
      +
      + + 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + <p>本文会介绍如何接入<code>filebase</code>的Names(IPNS)服务,使你的<code>IPFS</code>站点持久在线。</p> +<h4 id="背景">背景</h4> +<p>周末更新博客时,发现workflow的上传IPFS任务执行失败了。</p> + 本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      +

      背景

      +

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      +
      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      +
      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      +

      Github文档,官方已经不再更新了。

      +
      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      +

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      +

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      +

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      +

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      +

      折腾记录

      +

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      +

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      +
      生成密钥
      +

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      +

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      +

      所以需要将云主机的密钥导出后,导入到filebase

      +

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      +
      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      +

      + +chat + + +

      +

      查看已有密钥:

      +
      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      +
      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      +

      filebase导入key要求为base64编码,将其转为base64编码:

      +
      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      +
      创建NAME
      +

      进入filebase控制台,点击Create Name

      +

      + +input + + +

      +
      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      +
      修改workflow
      +
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      +

      提交后,执行workflow,在执行结果里找到IPNS地址。

      +
      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +
      +

      更新域名的dnslink值:

      +

      普通域名

      +

      + +dns + + +

      +

      eth域名

      +

      + +eth + + +

      +

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      +

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      +]]>
      +
      + + 一次简短的青岛之行 + https://liudon.com/posts/the-trip-of-qingdao/ + Sat, 31 Aug 2024 21:24:53 +0800 + https://liudon.com/posts/the-trip-of-qingdao/ + <p>刚放暑假的时候,就答应了娃带她去一趟青岛。</p> +<p>8月份要回老家,所以定在了7月中下旬出发。</p> +<p>车票/酒店都订好了,结果来了个台风格美。</p> +<p>出发前一周一直在查天气,就怕去了一直下雨。</p> + 刚放暑假的时候,就答应了娃带她去一趟青岛。

      +

      8月份要回老家,所以定在了7月中下旬出发。

      +

      车票/酒店都订好了,结果来了个台风格美。

      +

      出发前一周一直在查天气,就怕去了一直下雨。

      +

      看了台风的预测路径,感觉可能能赶在台风来之前的空档,硬着头皮出发吧。

      +

      7月26日乘坐高铁G203,中午12点左右到达青岛。

      +

      老天很给面子,是个晴天,还有点晒。

      +

      + +出行计划 + + +

      +

      本来的计划路线:

      +
      第一天:
      +
      +中午到达青岛 -> 天主教堂 -> 栈桥 -> 八大关 -> 第二海水浴场
      +
      +第二天:
      +
      +海底世界/极地海洋馆 -> 石老人海水浴场 -> 五四广场/奥帆中心夜景
      +
      +第三天:
      +
      +酒店休息返京
      +

      按这个路线,定了两个酒店,一个在栈桥附近,一个在海洋馆附近。

      +

      到了青岛后,先去酒店放行李,然后打车去吃饭。

      +

      青岛的第一顿饭,我们选了吃海鲜,事前查了些攻略,选择了栈桥附近的燕欣饭馆。

      +

      + +燕欣饭馆 + + +

      +

      中午很饿,上来就吃,忘记拍照了,只有吃完后的照片了。

      +

      + +一扫而光 + + +

      +

      油焖大虾相当不错,海肠捞饭非常好吃,韭菜很鲜。

      +

      3个人,一共花了240元,非常推荐的一家店。

      +

      吃完饭,溜达到天主教堂打卡。

      +

      + +天主教堂 + + +

      +

      然后是栈桥,人非常多,中午非常晒。

      +

      + +栈桥 + + +

      +

      于是回酒店稍作休息,决定打车直奔第二海水浴场玩水。

      +

      踩水的感觉太好玩了,娃从一开始的有点害怕,到后面追着水玩。

      +

      + +踩水 + + +

      +

      玩完打车去的美团推荐的双合园,地方很小,需要等位。

      +

      吃下来,感觉不如第一顿好吃,菜品一般,不太推荐。

      +

      吃完已经9点了,错过了夜景时间,直接回酒店休息了。

      +

      第二天起床,发现外面下雨了,风很大,最终没逃过台风的影响。

      +

      昨天路过海洋馆,看了外面排队的人巨多,决定不去室内这种海洋馆了。

      +

      先去了银鱼巷溜达一圈,没啥看的,中午在1907青岛老味道吃的午饭,非常不推荐的一家店。

      +

      吃完饭打车到奥帆中心,想着坐船玩一圈,到了发现因为风大停运了。

      +

      + +五四广场打卡 + + +

      +

      打卡完,直奔第三海水浴场玩水。

      +

      到了发现因为风大不让下水了,只能在沙滩上玩沙子了。

      +

      + +第三海水浴场 + + +

      +

      和娃一起抓了几只小螃蟹,虽然天气不好,娃玩的还是很开心。

      +

      + +赶海 + + +

      +

      实在不想吃海鲜了,晚饭在酒店附近吃了个米村拌饭,发现旁边有家类似北京的老年厨房,非常便宜,菜品也不错,第二天早晨在这里解决了。

      +

      天气不好,晚上在酒店看电视了,点了个麦当劳夜宵,结果娃没吃多少,全我吃了,给我撑的。

      +

      第三天天晴了,但是风还是大。

      +

      决定到第二海水浴场看看运气,到了之后还是不让下水,在沙滩上玩了会沙子。

      +

      时间差不多,回酒店办退房。

      +

      然后步行到火车站,上车回京。

      +

      青岛之行就此结束了,尽管行程很短,天气不太好,但一家人在一起就很开心,唯一的教训就是晚上夜宵不要吃的太多。 😂

      +]]>
      +
      + + 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ protoc-gen-go --version +</span></span><span style="display:flex;"><span>protoc-gen-go v1.34.2 +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ sh make.sh +</span></span><span style="display:flex;"><span>user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) +</span></span><span style="display:flex;"><span>$ +</span></span></code></pre></div><p>流水线编译报错,其中<code>make.sh</code>文件代码:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build +</span></span></code></pre></div><p>同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。</p> +
      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +$ 
      +$ protoc-gen-go --version
      +protoc-gen-go v1.34.2
      +$ 
      +$ sh make.sh
      +user.pb.go:123:45: undeclared name: any (requires version go1.18 or later)
      +$ 
      +

      流水线编译报错,其中make.sh文件代码:

      +
      ...
      +
      +protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto 
      +
      +...
      +
      +go build
      +

      同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。

      +

      登到流水线编译机器上,看了下go的版本已经是1.18.1了,按理不应该报这个错误的。

      +

      关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用master分支编译了一下,也还是报这个错误。

      +

      手动执行make.sh里的每条命令,发现是protoc编译pb文件时报的这个错误。

      +

      经过一番查找后,发现是protoc-gen-go在4月份更新了版本,引入了新特性。

      +

      protoc-gen-go’s versions

      +
      Versions in this module
      +v1
      +    v1.34.2 Jun 11, 2024
      +    v1.34.1 May 6, 2024
      +    v1.34.0 Apr 30, 2024
      +    v1.33.0 Mar 5, 2024
      +    v1.32.0 Dec 22, 2023
      +

      Protobuf Editions Overview

      +
      +

      Protobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = “proto2” or syntax = “proto3” at the top of proto definition files, you use an edition number, such as edition = “2024”, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.

      +

      Instead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.

      +
      +

      改用历史版本后解决。

      +
      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
      +
      ]]>
      +
      + + 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + Wed, 22 May 2024 21:20:20 +0800 + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + <h4 id="背景">背景</h4> +<p>4月底的时候,Livid大佬提醒,<code>Cloudflare</code>应该是调整了<code>IPFS Gateway</code>网关策略,我的<a href="https://liudon.xyz">IPFS镜像博客</a>无法访问了。</p> +<p>没查到<code>Cloudflare</code>的调整说明,不过还好<code>IPFS</code>官方也提供了公共网关<code>gateway.ipfs.io</code>,将域名解析改到官网网关。</p> + 背景 +

      4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。

      +

      没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。

      +

      但还是无法访问,被Cloudflare拦截了。

      +
      Error 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC
      +CNAME Cross-User Banned
      +What happened?
      +You've requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare's security policy.
      +
      +What can I do?
      +If this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2's documentation for details.
      +
      +
      +Visit our website to learn more about Cloudflare.
      +

      这周在Discord群里,看到有人发消息,说是Cloudflare将下线IPFS Gateway网关服务。

      +

      https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard

      +
      +

      All traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!

      +
      +

      方案调研

      +

      经过一番搜索,找到了一篇自建IPFS Gateway网关的资料,里面用到了bifrost-gateway组件。

      +
      To run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:
      +
      +$ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
      +

      看文档,可以通过这个命令搭建一个自己的网关服务,同时支持DNSLink方式访问。

      +

      太棒了,感觉可以自己搭一套网关,然后用Nginx反代对外提供服务。

      +

      在之前将博客部署到星际文件系统(IPFS)文章中,已经通过Kubo搭建了一套本地IPFS服务。

      +

      上机器验证一下可行性:

      +
        +
      1. +

        启动Bifrost Gateway,网关默认地址为https://127.0.0.1:8081

        +
        $ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
        +2024/05/22 20:54:00 Starting bifrost-gateway dev-build
        +2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080
        +2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024
        +2024/05/22 20:54:00 GRAPH_BACKEND: false
        +2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001
        +2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081
        +2024/05/22 20:54:00   Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
        +2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081
        +2024/05/22 20:54:00   Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/
        +2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus
        +
      2. +
      3. +

        在另外一个终端下,执行命令

        +
        $ curl 'http://127.0.0.1:8081/' -H"Host:liudon.xyz" -I
        +HTTP/1.1 200 OK
        +Accept-Ranges: bytes
        +Access-Control-Allow-Headers: Content-Type
        +Access-Control-Allow-Headers: Range
        +Access-Control-Allow-Headers: User-Agent
        +Access-Control-Allow-Headers: X-Requested-With
        +Access-Control-Allow-Methods: GET
        +Access-Control-Allow-Methods: HEAD
        +Access-Control-Allow-Methods: OPTIONS
        +Access-Control-Allow-Origin: *
        +Access-Control-Expose-Headers: Content-Length
        +Access-Control-Expose-Headers: Content-Range
        +Access-Control-Expose-Headers: X-Chunked-Output
        +Access-Control-Expose-Headers: X-Ipfs-Path
        +Access-Control-Expose-Headers: X-Ipfs-Roots
        +Access-Control-Expose-Headers: X-Stream-Output
        +Content-Length: 26283
        +Content-Type: text/html
        +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
        +Last-Modified: Wed, 22 May 2024 12:57:29 GMT
        +X-Ipfs-Path: /ipns/liudon.xyz/
        +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
        +Date: Wed, 22 May 2024 12:57:29 GMT
        +
      4. +
      +

      验证可行,不过我记得Kubo默认就有网关服务的,试一下直接通过Kubo默认网关的情况。

      +

      Kubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用

      +
      $ curl 'http://127.0.0.1:8080/' -H"Host:liudon.xyz" -I
      +HTTP/1.1 200 OK
      +Accept-Ranges: bytes
      +Access-Control-Allow-Headers: Content-Type
      +Access-Control-Allow-Headers: Range
      +Access-Control-Allow-Headers: User-Agent
      +Access-Control-Allow-Headers: X-Requested-With
      +Access-Control-Allow-Methods: GET
      +Access-Control-Allow-Origin: *
      +Access-Control-Expose-Headers: Content-Length
      +Access-Control-Expose-Headers: Content-Range
      +Access-Control-Expose-Headers: X-Chunked-Output
      +Access-Control-Expose-Headers: X-Ipfs-Path
      +Access-Control-Expose-Headers: X-Ipfs-Roots
      +Access-Control-Expose-Headers: X-Stream-Output
      +Content-Length: 26283
      +Content-Type: text/html
      +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
      +Last-Modified: Wed, 22 May 2024 12:59:25 GMT
      +X-Ipfs-Path: /ipns/liudon.xyz/
      +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
      +Date: Wed, 22 May 2024 12:59:25 GMT
      +

      也是可以的,那就没必要多搞一套bifrost网关了。

      +

      具体实现

      +

      通过Nginx反向代理转发到本地IPFS网关,只需要改一下解析就可以继续使用IPFS服务了。

      +

      + +方案 + + +

      +
        +
      1. Nginx反向代理
      2. +
      +
      server {
      +    listen 443 ssl http2;
      +    server_name liudon.xyz;
      +
      +    ssl_certificate /etc/nginx/ssl/liudon.xyz/fullchain.cer;
      +    ssl_certificate_key /etc/nginx/ssl/liudon.xyz/liudon.xyz.key;
      +
      +    ssl_protocols TLSv1.2 TLSv1.3;
      +    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
      +    ssl_prefer_server_ciphers on;
      +    ssl_session_cache shared:SSL:10m;
      +    ssl_session_timeout 10m;
      +
      +    location / {
      +            proxy_pass http://127.0.0.1:8080;
      +            proxy_set_header Host $host; // 注意这里要传递反代的域名信息,限制只能访问我们自己dnslink对应的资源
      +    }
      +
      +    access_log /var/log/nginx/liudon.xyz.access.log;
      +    error_log /var/log/nginx/liudon.xyz.error.log;
      +}
      +

      申请Let's Encrypt证书,证书相关的就不多做介绍了,网上资料很多。

      +
        +
      1. 更改DNS解析
      2. +
      +
      原有的解析
      +
      +类型:CNAME
      +名称:liudon.xyz
      +内容:cloudflare-ipfs.com
      +
      +新的解析
      +
      +类型:A
      +名称:liudon.xyz
      +内容:你的服务器公网IP
      +

      搞定,又可以继续白嫖IPFS服务了。

      +]]>
      +
      + + 302跳转的跨域问题(CORS) + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + Fri, 17 May 2024 20:13:57 +0800 + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + <p>302跳转的跨域问题</p> +<h4 id="场景一302不返回跨域头">场景一:302不返回跨域头</h4> +<p><strong>请求</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>GET /302 HTTP/1.1 +</span></span><span style="display:flex;"><span>Host: liudon.xyz +</span></span><span style="display:flex;"><span>Origin: https://www.baidu.com +</span></span><span style="display:flex;"><span>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 +</span></span></code></pre></div><p><strong>返回</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>HTTP/1.1 200 OK +</span></span><span style="display:flex;"><span>Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 +</span></span><span style="display:flex;"><span>Cf-Ray: 88535773eaf5107e-HKG +</span></span><span style="display:flex;"><span>Content-Length: 143 +</span></span><span style="display:flex;"><span>Content-Type: text/html +</span></span><span style="display:flex;"><span>Date: Fri, 17 May 2024 11:42:00 GMT +</span></span><span style="display:flex;"><span>Expires: Thu, 01 Jan 1970 00:00:01 GMT +</span></span><span style="display:flex;"><span>Location: https://liudon.org +</span></span><span style="display:flex;"><span>Server: cloudflare +</span></span><span style="display:flex;"><span>Vary: Accept-Encoding +</span></span></code></pre></div><p><strong>浏览器报错</strong></p> + 302跳转的跨域问题

      +

      场景一:302不返回跨域头

      +

      请求

      +
      GET /302 HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.xyz/302' from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 + + +

      +

      场景二:302跳转返回跨域头

      +

      请求

      +
      GET /302_return_origin_header HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Access-Control-Allow-Origin: https://www.baidu.com
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.org/' (redirected from 'https://liudon.xyz/302_return_origin_header') from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 return origin header + + +

      +

      注意,这里302跳转请求没有报错,是跳转后的连接报了跨域错误。

      +

      Location请求

      +
      GET / HTTP/1.1
      +Host: liudon.org
      +Origin: null
      +Referer: https://www.baidu.com/
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      + +location + + +

      +

      302返回了跨域头,所以浏览器请求了Location地址。

      +

      但为什么两次请求header头里的Origin字段值不一致呢?第二次Location请求为什么Origin字段值是null?

      +
      第一次:
      +Origin: https://www.baidu.com
      +
      +第二次
      +Origin: null
      +

      经过一番搜索,终于找到了一些资料。

      +
      +

      The Origin header value may be null in a number of cases, including (non-exhaustively):

      +

      Origins whose scheme is not one of http, https, ftp, ws, wss, or gopher (including blob, file and data). +Cross-origin images and media data, including that in , and elements. +Documents created programmatically using createDocument(), generated from a data: URL, or that do not have a creator browsing context. +Redirects across origins. +iframes with a sandbox attribute that doesn’t contain the value allow-same-origin. +Responses that are network errors. +Referrer-Policy set to no-referrer for non-cors request modes (e.g. simple form posts).

      +
      +

      出自 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description

      +
      +

      A request request has a redirect-tainted origin if these steps return true:

      +

      Let lastURL be null.

      +

      For each url of request’s URL list:

      +

      If lastURL is null, then set lastURL to url and continue.

      +

      If url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true.

      +

      Set lastURL to url. +Return false. +Serializing a request origin, given a request request, is to run these steps:

      +

      If request has a redirect-tainted origin, then return “null”.

      +

      Return request’s origin, serialized.

      +
      +

      出自 https://fetch.spec.whatwg.org/#concept-request-tainted-origin

      +

      简单说就是如果302跳转的域与上一次请求域不同的话,就会将Origin设置为null

      +]]>
      +
      + + GORM增加sqlcommenter特性 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + <p>什么是sqlcommenter?</p> +<blockquote> +<p>sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.</p> + 什么是sqlcommenter?

      +
      +

      sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

      +
      +

      GORM提供了hints组件,可以支持sqlcommenter

      +
      import "gorm.io/hints"
      +
      +DB.Clauses(hints.Comment("select", "master")).Find(&User{})
      +// SELECT /*master*/ * FROM `users`;
      +
      +DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
      +// SELECT * FROM `users` WHERE id = ? /* hint */
      +

      但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

      +

      我希望是全局增加sqlcommenter,业务侧不需要过多调整。

      +

      完整代码如下:

      +
      plugins/gorm.go
      +
      +package plugins
      +
      +import (
      +	"fmt"
      +
      +	gorm "gorm.io/gorm"
      +	gormclause "gorm.io/gorm/clause"
      +)
      +
      +type Comment struct {
      +	Content string
      +}
      +
      +func (c Comment) Name() string {
      +	return "COMMENT"
      +}
      +
      +func (c Comment) Build(builder gormclause.Builder) {
      +	builder.WriteString("/* ")
      +	builder.WriteString(c.Content)
      +	builder.WriteString(" */")
      +}
      +
      +func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
      +}
      +
      +func (c Comment) ModifyStatement(stmt *gorm.Statement) {
      +	clause := stmt.Clauses[c.Name()]
      +    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
      +    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
      +	clause.Expression = c
      +	stmt.Clauses[c.Name()] = clause
      +}
      +
      +var extraClause = []string{"COMMENT"}
      +
      +type CommentClausePlugin struct{}
      +
      +// NewCommentClausePlugin create a new ExtraPlugin
      +func NewCommentClausePlugin() *CommentClausePlugin {
      +	return &CommentClausePlugin{}
      +}
      +
      +// Name plugin name
      +func (ep *CommentClausePlugin) Name() string {
      +	return "CommentClausePlugin"
      +}
      +
      +// Initialize register BuildClauses
      +func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
      +	initClauses(db)
      +	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)
      +
      +	return
      +}
      +
      +func AddAnnotation(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +
      +	rid := "xx"
      +	// context上下文里取rid信息
      +	if v, ok := db.Statement.Context.Value("rid").(string); ok {
      +		rid = v
      +	}
      +
      +	content := fmt.Sprintf("rid=%s", rid)
      +
      +	if db.Statement.SQL.Len() > 0 {
      +		oldSQL := db.Statement.SQL.String()
      +		db.Statement.SQL.Reset()
      +		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
      +		return
      +	}
      +
      +	db.Statement.AddClause(Comment{Content: content})
      +}
      +
      +// initClauses init SQL clause
      +func initClauses(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +	createClause := append(extraClause, db.Callback().Create().Clauses...)
      +	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
      +	queryClause := append(extraClause, db.Callback().Query().Clauses...)
      +	updateClause := append(extraClause, db.Callback().Update().Clauses...)
      +	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
      +	rowClause := append(extraClause, db.Callback().Row().Clauses...)
      +	db.Callback().Create().Clauses = createClause
      +	db.Callback().Delete().Clauses = deleteClause
      +	db.Statement.Callback().Query().Clauses = queryClause
      +	db.Callback().Update().Clauses = updateClause
      +	db.Callback().Raw().Clauses = rawClause
      +	db.Callback().Row().Clauses = rowClause
      +}
      +
      +
      +main.go
      +package main
      +
      +import (
      +    "context"
      +    "plugins"
      +
      +    gorm "gorm.io/gorm"
      +    "github.com/google/uuid"
      +)
      +
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +
      +    db.Use(plugins.NewCommentClausePlugin())
      +
      +    db.Create(&Product{Code: "D42", Price: 100})
      +
      +    // 传入context,指定rid
      +    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
      +    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
      +}
      +

      阻塞了两天的问题,终于解决了!😁😁😁

      +

      how gorm generates sql

      +]]>
      +
      + + 源码分析:GORM是如何生成sql的 + https://liudon.com/posts/how-gorm-generates-sql/ + Thu, 18 Apr 2024 21:14:24 +0800 + https://liudon.com/posts/how-gorm-generates-sql/ + <p>在<code>gorm</code>下实现<a href="https://google.github.io/sqlcommenter/">sqlcommenter</a>过程中,遇到一些问题,顺便把<code>gorm</code>整个流程梳理了一遍,整理记录一下。</p> +<p>gorm使用示例</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>package main +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>import ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/driver/mysql&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/gorm&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>type Product struct { +</span></span><span style="display:flex;"><span> gorm<span style="color:#f92672">.</span>Model +</span></span><span style="display:flex;"><span> Code string +</span></span><span style="display:flex;"><span> Price uint +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> main() { +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">参考</span> https:<span style="color:#f92672">//</span>github<span style="color:#f92672">.</span>com<span style="color:#f92672">/</span>go<span style="color:#f92672">-</span>sql<span style="color:#f92672">-</span>driver<span style="color:#f92672">/</span>mysql<span style="color:#75715e">#dsn-data-source-name 获取详情</span> +</span></span><span style="display:flex;"><span> dsn :<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&amp;parseTime=True&amp;loc=Local&#34;</span> +</span></span><span style="display:flex;"><span> db, err :<span style="color:#f92672">=</span> gorm<span style="color:#f92672">.</span>Open(mysql<span style="color:#f92672">.</span>Open(dsn), <span style="color:#f92672">&amp;</span>gorm<span style="color:#f92672">.</span>Config{}) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> product Product +</span></span><span style="display:flex;"><span> db<span style="color:#f92672">.</span>First(<span style="color:#f92672">&amp;</span>product, <span style="color:#ae81ff">1</span>) <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">根据整型主键查找</span> +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>我们以<code>First</code>查询为例,看一下是怎么转成具体sql的。</p> + gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。

      +

      gorm使用示例

      +
      package main
      +
      +import (
      +  "gorm.io/driver/mysql"
      +  "gorm.io/gorm"
      +)
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
      +  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +  
      +  var product Product
      +  db.First(&product, 1) // 根据整型主键查找
      +}
      +

      我们以First查询为例,看一下是怎么转成具体sql的。

      +

      finisher_api.go文件,声明了First方法。

      +
      // First finds the first record ordered by primary key, matching given conditions conds
      +func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
      +	// 注册Order类型的Clause
      +	tx = db.Limit(1).Order(clause.OrderByColumn{
      +		Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
      +	})
      +	// 这里如果有指定条件的话,注册一个Where类型的Clause
      +	if len(conds) > 0 {
      +		if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
      +			tx.Statement.AddClause(clause.Where{Exprs: exprs})
      +		}
      +	}
      +	tx.Statement.RaiseErrorOnNotFound = true
      +	tx.Statement.Dest = dest
      +	return tx.callbacks.Query().Execute(tx)
      +}
      +

      gorm.go文件,可以找到tx.callbacks定义。

      +
      type Config struct {
      +	...
      +
      +	callbacks  *callbacks
      +	cacheStore *sync.Map
      +}
      +

      callbacks.go

      +
      // callbacks gorm callbacks manager
      +type callbacks struct {
      +	processors map[string]*processor
      +}
      +
      +type processor struct {
      +	db        *DB
      +	Clauses   []string
      +	fns       []func(*DB)
      +	callbacks []*callback
      +}
      +
      +type callback struct {
      +	name      string
      +	before    string
      +	after     string
      +	remove    bool
      +	replace   bool
      +	match     func(*DB) bool
      +	handler   func(*DB)
      +	processor *processor
      +}
      +
      +// 返回query类型的processor
      +func (cs *callbacks) Query() *processor {
      +	return cs.processors["query"]
      +}
      +
      +func (p *processor) Execute(db *DB) *DB {
      +	// call scopes
      +	for len(db.Statement.scopes) > 0 {
      +		db = db.executeScopes()
      +	}
      +
      +	var (
      +		curTime           = time.Now()
      +		stmt              = db.Statement
      +		resetBuildClauses bool
      +	)
      +
      +	// 注意这里的stmt.BuildClauses,后面会用到这个信息
      +	if len(stmt.BuildClauses) == 0 {
      +		stmt.BuildClauses = p.Clauses
      +		resetBuildClauses = true
      +	}
      +
      +	if optimizer, ok := db.Statement.Dest.(StatementModifier); ok {
      +		optimizer.ModifyStatement(stmt)
      +	}
      +
      +	// assign model values
      +	if stmt.Model == nil {
      +		stmt.Model = stmt.Dest
      +	} else if stmt.Dest == nil {
      +		stmt.Dest = stmt.Model
      +	}
      +
      +	// parse model values
      +	if stmt.Model != nil {
      +		if err := stmt.Parse(stmt.Model); err != nil && (!errors.Is(err, schema.ErrUnsupportedDataType) || (stmt.Table == "" && stmt.TableExpr == nil && stmt.SQL.Len() == 0)) {
      +			if errors.Is(err, schema.ErrUnsupportedDataType) && stmt.Table == "" && stmt.TableExpr == nil {
      +				db.AddError(fmt.Errorf("%w: Table not set, please set it like: db.Model(&user) or db.Table(\"users\")", err))
      +			} else {
      +				db.AddError(err)
      +			}
      +		}
      +	}
      +
      +	// assign stmt.ReflectValue
      +	if stmt.Dest != nil {
      +		stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
      +		for stmt.ReflectValue.Kind() == reflect.Ptr {
      +			if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
      +				stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
      +			}
      +
      +			stmt.ReflectValue = stmt.ReflectValue.Elem()
      +		}
      +		if !stmt.ReflectValue.IsValid() {
      +			db.AddError(ErrInvalidValue)
      +		}
      +	}
      +
      +	// 根据优先级执行不同callback的回调方法
      +	for _, f := range p.fns {
      +		f(db)
      +	}
      +
      +	if stmt.SQL.Len() > 0 {
      +		db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
      +			sql, vars := stmt.SQL.String(), stmt.Vars
      +			if filter, ok := db.Logger.(ParamsFilter); ok {
      +				sql, vars = filter.ParamsFilter(stmt.Context, stmt.SQL.String(), stmt.Vars...)
      +			}
      +			return db.Dialector.Explain(sql, vars...), db.RowsAffected
      +		}, db.Error)
      +	}
      +
      +	if !stmt.DB.DryRun {
      +		stmt.SQL.Reset()
      +		stmt.Vars = nil
      +	}
      +
      +	if resetBuildClauses {
      +		stmt.BuildClauses = nil
      +	}
      +
      +	return db
      +}
      +

      接下来,我们来看一下内置的callback是如何注册的。

      +

      mysql.go

      +
      var (
      +	// CreateClauses create clauses
      +	CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	// QueryClauses query clauses
      +	QueryClauses = []string{}
      +	// UpdateClauses update clauses
      +	UpdateClauses = []string{"UPDATE", "SET", "WHERE", "ORDER BY", "LIMIT"}
      +	// DeleteClauses delete clauses
      +	DeleteClauses = []string{"DELETE", "FROM", "WHERE", "ORDER BY", "LIMIT"}
      +
      +	defaultDatetimePrecision = 3
      +)
      +
      +...
      +
      +func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
      +	if dialector.DriverName == "" {
      +		dialector.DriverName = "mysql"
      +	}
      +
      +	if dialector.DefaultDatetimePrecision == nil {
      +		dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
      +	}
      +
      +	if dialector.Conn != nil {
      +		db.ConnPool = dialector.Conn
      +	} else {
      +		db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
      +		if err != nil {
      +			return err
      +		}
      +	}
      +
      +	withReturning := false
      +	if !dialector.Config.SkipInitializeWithVersion {
      +		err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
      +		if err != nil {
      +			return err
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "MariaDB") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportNullAsDefaultValue = true
      +			withReturning = checkVersion(dialector.ServerVersion, "10.5")
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.7.") {
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.") {
      +			dialector.Config.DisableDatetimePrecision = true
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "TiDB") {
      +			dialector.Config.DontSupportRenameColumnUnique = true
      +		}
      +	}
      +
      +	// register callbacks
      +	callbackConfig := &callbacks.Config{
      +		CreateClauses: CreateClauses,
      +		QueryClauses:  QueryClauses,
      +		UpdateClauses: UpdateClauses,
      +		DeleteClauses: DeleteClauses,
      +	}
      +
      +	if !dialector.Config.DisableWithReturning && withReturning {
      +		if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") {
      +			callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") {
      +			callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") {
      +			callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
      +		}
      +	}
      +
      +	// 注册默认callback
      +	callbacks.RegisterDefaultCallbacks(db, callbackConfig)
      +
      +	for k, v := range dialector.ClauseBuilders() {
      +		db.ClauseBuilders[k] = v
      +	}
      +	return
      +}
      +

      callbacks.go

      +
      var (
      +	createClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	queryClauses  = []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"}
      +	updateClauses = []string{"UPDATE", "SET", "WHERE"}
      +	deleteClauses = []string{"DELETE", "FROM", "WHERE"}
      +)
      +
      +type Config struct {
      +	LastInsertIDReversed bool
      +	CreateClauses        []string
      +	QueryClauses         []string
      +	UpdateClauses        []string
      +	DeleteClauses        []string
      +}
      +
      +func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
      +	enableTransaction := func(db *gorm.DB) bool {
      +		return !db.SkipDefaultTransaction
      +	}
      +
      +	if len(config.CreateClauses) == 0 {
      +		config.CreateClauses = createClauses
      +	}
      +	if len(config.QueryClauses) == 0 {
      +		config.QueryClauses = queryClauses
      +	}
      +	if len(config.DeleteClauses) == 0 {
      +		config.DeleteClauses = deleteClauses
      +	}
      +	if len(config.UpdateClauses) == 0 {
      +		config.UpdateClauses = updateClauses
      +	}
      +
      +    // 注册不同类型的callback
      +	createCallback := db.Callback().Create()
      +	createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	createCallback.Register("gorm:before_create", BeforeCreate)
      +	createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
      +	createCallback.Register("gorm:create", Create(config))
      +	createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
      +	createCallback.Register("gorm:after_create", AfterCreate)
      +	createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	createCallback.Clauses = config.CreateClauses
      +
      +	queryCallback := db.Callback().Query()
      +	queryCallback.Register("gorm:query", Query)
      +	queryCallback.Register("gorm:preload", Preload)
      +	queryCallback.Register("gorm:after_query", AfterQuery)
      +	queryCallback.Clauses = config.QueryClauses
      +
      +	deleteCallback := db.Callback().Delete()
      +	deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	deleteCallback.Register("gorm:before_delete", BeforeDelete)
      +	deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
      +	deleteCallback.Register("gorm:delete", Delete(config))
      +	deleteCallback.Register("gorm:after_delete", AfterDelete)
      +	deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	deleteCallback.Clauses = config.DeleteClauses
      +
      +	updateCallback := db.Callback().Update()
      +	updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
      +	updateCallback.Register("gorm:before_update", BeforeUpdate)
      +	updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
      +	updateCallback.Register("gorm:update", Update(config))
      +	updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
      +	updateCallback.Register("gorm:after_update", AfterUpdate)
      +	updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	updateCallback.Clauses = config.UpdateClauses
      +
      +	rowCallback := db.Callback().Row()
      +	rowCallback.Register("gorm:row", RowQuery)
      +	rowCallback.Clauses = config.QueryClauses
      +
      +	rawCallback := db.Callback().Raw()
      +	rawCallback.Register("gorm:raw", RawExec)
      +	rawCallback.Clauses = config.QueryClauses
      +}
      +

      到这里,默认callback就注册完成了,但是是如何转成对应sql的呢?

      +

      别急,我们继续往下看。

      +

      RegisterDefaultCallbacks方法里注册了一个gorm:query类型的callback,对应的回调方法为Query

      +

      query.go

      +
      func Query(db *gorm.DB) {
      +	if db.Error == nil {
      +		// 调用BuildQuerySQL方法
      +		BuildQuerySQL(db)
      +
      +		if !db.DryRun && db.Error == nil {
      +			rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
      +			if err != nil {
      +				db.AddError(err)
      +				return
      +			}
      +			defer func() {
      +				db.AddError(rows.Close())
      +			}()
      +			gorm.Scan(rows, db, 0)
      +		}
      +	}
      +}
      +
      +func BuildQuerySQL(db *gorm.DB) {
      +	if db.Statement.Schema != nil {
      +		for _, c := range db.Statement.Schema.QueryClauses {
      +			db.Statement.AddClause(c)
      +		}
      +	}
      +
      +	if db.Statement.SQL.Len() == 0 {
      +		db.Statement.SQL.Grow(100)
      +		clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
      +
      +		if db.Statement.ReflectValue.Kind() == reflect.Struct && db.Statement.ReflectValue.Type() == db.Statement.Schema.ModelType {
      +			var conds []clause.Expression
      +			for _, primaryField := range db.Statement.Schema.PrimaryFields {
      +				if v, isZero := primaryField.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
      +					conds = append(conds, clause.Eq{Column: clause.Column{Table: db.Statement.Table, Name: primaryField.DBName}, Value: v})
      +				}
      +			}
      +
      +			if len(conds) > 0 {
      +				db.Statement.AddClause(clause.Where{Exprs: conds})
      +			}
      +		}
      +
      +		if len(db.Statement.Selects) > 0 {
      +			clauseSelect.Columns = make([]clause.Column, len(db.Statement.Selects))
      +			for idx, name := range db.Statement.Selects {
      +				if db.Statement.Schema == nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				} else if f := db.Statement.Schema.LookUpField(name); f != nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: f.DBName}
      +				} else {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && len(db.Statement.Omits) > 0 {
      +			selectColumns, _ := db.Statement.SelectAndOmitColumns(false, false)
      +			clauseSelect.Columns = make([]clause.Column, 0, len(db.Statement.Schema.DBNames))
      +			for _, dbName := range db.Statement.Schema.DBNames {
      +				if v, ok := selectColumns[dbName]; (ok && v) || !ok {
      +					clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{Table: db.Statement.Table, Name: dbName})
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && db.Statement.ReflectValue.IsValid() {
      +			queryFields := db.QueryFields
      +			if !queryFields {
      +				switch db.Statement.ReflectValue.Kind() {
      +				case reflect.Struct:
      +					queryFields = db.Statement.ReflectValue.Type() != db.Statement.Schema.ModelType
      +				case reflect.Slice:
      +					queryFields = db.Statement.ReflectValue.Type().Elem() != db.Statement.Schema.ModelType
      +				}
      +			}
      +
      +			if queryFields {
      +				stmt := gorm.Statement{DB: db}
      +				// smaller struct
      +				if err := stmt.Parse(db.Statement.Dest); err == nil && (db.QueryFields || stmt.Schema.ModelType != db.Statement.Schema.ModelType) {
      +					clauseSelect.Columns = make([]clause.Column, len(stmt.Schema.DBNames))
      +
      +					for idx, dbName := range stmt.Schema.DBNames {
      +						clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +					}
      +				}
      +			}
      +		}
      +
      +		// inline joins
      +		fromClause := clause.From{}
      +		if v, ok := db.Statement.Clauses["FROM"].Expression.(clause.From); ok {
      +			fromClause = v
      +		}
      +
      +		if len(db.Statement.Joins) != 0 || len(fromClause.Joins) != 0 {
      +			if len(db.Statement.Selects) == 0 && len(db.Statement.Omits) == 0 && db.Statement.Schema != nil {
      +				clauseSelect.Columns = make([]clause.Column, len(db.Statement.Schema.DBNames))
      +				for idx, dbName := range db.Statement.Schema.DBNames {
      +					clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +				}
      +			}
      +
      +			specifiedRelationsName := make(map[string]interface{})
      +			for _, join := range db.Statement.Joins {
      +				if db.Statement.Schema != nil {
      +					var isRelations bool // is relations or raw sql
      +					var relations []*schema.Relationship
      +					relation, ok := db.Statement.Schema.Relationships.Relations[join.Name]
      +					if ok {
      +						isRelations = true
      +						relations = append(relations, relation)
      +					} else {
      +						// handle nested join like "Manager.Company"
      +						nestedJoinNames := strings.Split(join.Name, ".")
      +						if len(nestedJoinNames) > 1 {
      +							isNestedJoin := true
      +							gussNestedRelations := make([]*schema.Relationship, 0, len(nestedJoinNames))
      +							currentRelations := db.Statement.Schema.Relationships.Relations
      +							for _, relname := range nestedJoinNames {
      +								// incomplete match, only treated as raw sql
      +								if relation, ok = currentRelations[relname]; ok {
      +									gussNestedRelations = append(gussNestedRelations, relation)
      +									currentRelations = relation.FieldSchema.Relationships.Relations
      +								} else {
      +									isNestedJoin = false
      +									break
      +								}
      +							}
      +
      +							if isNestedJoin {
      +								isRelations = true
      +								relations = gussNestedRelations
      +							}
      +						}
      +					}
      +
      +					if isRelations {
      +						genJoinClause := func(joinType clause.JoinType, parentTableName string, relation *schema.Relationship) clause.Join {
      +							tableAliasName := relation.Name
      +							if parentTableName != clause.CurrentTable {
      +								tableAliasName = utils.NestedRelationName(parentTableName, tableAliasName)
      +							}
      +
      +							columnStmt := gorm.Statement{
      +								Table: tableAliasName, DB: db, Schema: relation.FieldSchema,
      +								Selects: join.Selects, Omits: join.Omits,
      +							}
      +
      +							selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
      +							for _, s := range relation.FieldSchema.DBNames {
      +								if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
      +									clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
      +										Table: tableAliasName,
      +										Name:  s,
      +										Alias: utils.NestedRelationName(tableAliasName, s),
      +									})
      +								}
      +							}
      +
      +							exprs := make([]clause.Expression, len(relation.References))
      +							for idx, ref := range relation.References {
      +								if ref.OwnPrimaryKey {
      +									exprs[idx] = clause.Eq{
      +										Column: clause.Column{Table: parentTableName, Name: ref.PrimaryKey.DBName},
      +										Value:  clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +									}
      +								} else {
      +									if ref.PrimaryValue == "" {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: parentTableName, Name: ref.ForeignKey.DBName},
      +											Value:  clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName},
      +										}
      +									} else {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +											Value:  ref.PrimaryValue,
      +										}
      +									}
      +								}
      +							}
      +
      +							{
      +								onStmt := gorm.Statement{Table: tableAliasName, DB: db, Clauses: map[string]clause.Clause{}}
      +								for _, c := range relation.FieldSchema.QueryClauses {
      +									onStmt.AddClause(c)
      +								}
      +
      +								if join.On != nil {
      +									onStmt.AddClause(join.On)
      +								}
      +
      +								if cs, ok := onStmt.Clauses["WHERE"]; ok {
      +									if where, ok := cs.Expression.(clause.Where); ok {
      +										where.Build(&onStmt)
      +
      +										if onSQL := onStmt.SQL.String(); onSQL != "" {
      +											vars := onStmt.Vars
      +											for idx, v := range vars {
      +												bindvar := strings.Builder{}
      +												onStmt.Vars = vars[0 : idx+1]
      +												db.Dialector.BindVarTo(&bindvar, &onStmt, v)
      +												onSQL = strings.Replace(onSQL, bindvar.String(), "?", 1)
      +											}
      +
      +											exprs = append(exprs, clause.Expr{SQL: onSQL, Vars: vars})
      +										}
      +									}
      +								}
      +							}
      +
      +							return clause.Join{
      +								Type:  joinType,
      +								Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName},
      +								ON:    clause.Where{Exprs: exprs},
      +							}
      +						}
      +
      +						parentTableName := clause.CurrentTable
      +						for _, rel := range relations {
      +							// joins table alias like "Manager, Company, Manager__Company"
      +							nestedAlias := utils.NestedRelationName(parentTableName, rel.Name)
      +							if _, ok := specifiedRelationsName[nestedAlias]; !ok {
      +								fromClause.Joins = append(fromClause.Joins, genJoinClause(join.JoinType, parentTableName, rel))
      +								specifiedRelationsName[nestedAlias] = nil
      +							}
      +
      +							if parentTableName != clause.CurrentTable {
      +								parentTableName = utils.NestedRelationName(parentTableName, rel.Name)
      +							} else {
      +								parentTableName = rel.Name
      +							}
      +						}
      +					} else {
      +						fromClause.Joins = append(fromClause.Joins, clause.Join{
      +							Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +						})
      +					}
      +				} else {
      +					fromClause.Joins = append(fromClause.Joins, clause.Join{
      +						Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +					})
      +				}
      +			}
      +
      +			db.Statement.AddClause(fromClause)
      +		} else {
      +			db.Statement.AddClauseIfNotExists(clause.From{})
      +		}
      +
      +		db.Statement.AddClauseIfNotExists(clauseSelect)
      +
      +		// db.Statement.BuildClauses眼熟吗?还记得前面的stmt.BuildClauses吗
      +		db.Statement.Build(db.Statement.BuildClauses...)
      +	}
      +}
      +

      重头戏终于来了,Query方法里调用了BuildQuerySQl,看名字也能猜到这里就是生成sql了,这里最终调用了db.Statement.Build方法。

      +

      statement.go

      +
      // Build build sql with clauses names
      +func (stmt *Statement) Build(clauses ...string) {
      +	var firstClauseWritten bool
      +
      +	for _, name := range clauses {
      +		if c, ok := stmt.Clauses[name]; ok {
      +			if firstClauseWritten {
      +				stmt.WriteByte(' ')
      +			}
      +
      +			firstClauseWritten = true
      +			if b, ok := stmt.DB.ClauseBuilders[name]; ok {
      +				b(c, stmt)
      +			} else {
      +				c.Build(stmt)
      +			}
      +		}
      +	}
      +}
      +

      这里会根据statementBuildCluauses属性,执行ClauseBuild方法。

      +

      clause.go

      +
      // ClauseBuilder clause builder, allows to customize how to build clause
      +type ClauseBuilder func(Clause, Builder)
      +
      +type Writer interface {
      +	WriteByte(byte) error
      +	WriteString(string) (int, error)
      +}
      +
      +// Builder builder interface
      +type Builder interface {
      +	Writer
      +	WriteQuoted(field interface{})
      +	AddVar(Writer, ...interface{})
      +	AddError(error) error
      +}
      +
      +// Clause
      +type Clause struct {
      +	Name                string // WHERE
      +	BeforeExpression    Expression
      +	AfterNameExpression Expression
      +	AfterExpression     Expression
      +	Expression          Expression
      +	Builder             ClauseBuilder
      +}
      +
      +// Build build clause
      +func (c Clause) Build(builder Builder) {
      +	if c.Builder != nil {
      +		c.Builder(c, builder)
      +	} else if c.Expression != nil {
      +		if c.BeforeExpression != nil {
      +			c.BeforeExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.Name != "" {
      +			builder.WriteString(c.Name)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.AfterNameExpression != nil {
      +			c.AfterNameExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		c.Expression.Build(builder)
      +
      +		if c.AfterExpression != nil {
      +			builder.WriteByte(' ')
      +			c.AfterExpression.Build(builder)
      +		}
      +	}
      +}
      +

      这里会执行对应Clause的Build方法。

      +
      // Select select attrs when querying, updating, creating
      +type Select struct {
      +	Distinct   bool
      +	Columns    []Column
      +	Expression Expression
      +}
      +
      +func (s Select) Name() string {
      +	return "SELECT"
      +}
      +
      +func (s Select) Build(builder Builder) {
      +	if len(s.Columns) > 0 {
      +		if s.Distinct {
      +			builder.WriteString("DISTINCT ")
      +		}
      +
      +		for idx, column := range s.Columns {
      +			if idx > 0 {
      +				builder.WriteByte(',')
      +			}
      +			builder.WriteQuoted(column)
      +		}
      +	} else {
      +		builder.WriteByte('*')
      +	}
      +}
      +
      +func (s Select) MergeClause(clause *Clause) {
      +	if s.Expression != nil {
      +		if s.Distinct {
      +			if expr, ok := s.Expression.(Expr); ok {
      +				expr.SQL = "DISTINCT " + expr.SQL
      +				clause.Expression = expr
      +				return
      +			}
      +		}
      +
      +		clause.Expression = s.Expression
      +	} else {
      +		clause.Expression = s
      +	}
      +}
      +

      这是Select类型的Clause定义,是不是一下就清楚了。

      +

      gorm通过callback里注册Clause,在Clause里实现了sql拼接操作。

      +

      看了几回源码,这次总算是搞清楚了。

      +]]>
      +
      + + 工银亚洲网银密码重置 + https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/ + Sat, 16 Mar 2024 10:09:59 +0800 + https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/ + <p>18年的时候办了张工银亚洲的银行卡,好几年没有用过了。</p> +<p>今年想起来了,发现网银登不上了,密码忘了。</p> +<p>最悲剧的是,试了超过10次,账户冻结了。</p> +<p>打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。</p> + 18年的时候办了张工银亚洲的银行卡,好几年没有用过了。

      +

      今年想起来了,发现网银登不上了,密码忘了。

      +

      最悲剧的是,试了超过10次,账户冻结了。

      +

      打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。

      +

      1月27日,东城出入境办理港澳通行证签注,排队半个小时左右,自助机操作。

      +

      顺路跑到附近可以见证开户的工行,说需要香港那边配合,人周末不上班,只能工作日。

      +

      电话西二旗支行,说是可以早上早点来办理,要不过年人多,没时间处理。

      +

      1月30日,早上直奔银行,赶在开门前到,第一个办理。

      +

      工作人员会指导你填写资料,填完后会给你两个信封,里面是重置后的网银密码。

      +

      1月31日下午,95588打电话,我以为是广告没接。

      +

      第二天早上反应过来,在路上的时候又打过来了。

      +

      香港那边直接电话,跟你确认身份信息后,下午就收到工行发来的已更新资料邮件。

      +

      其实这里密码就重置完了,因为我同时办理了更新通信地址,我以为是只更新了地址。

      +

      至此,整个流程就办完了,还是非常方便的。

      +

      另外,发现招银亚洲也没法登陆了,U盾过期了,又跑了一趟招行,重新办了个U盾。

      +

      好了,这下两个银行都可以正常使用了。

      +]]>
      +
      + + 加速Cloudflare访问 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + Wed, 21 Feb 2024 20:25:49 +0800 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + <h4 id="背景">背景</h4> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="blog.png" width="828" height="753" alt="博客架构" title="" loading="lazy" /> + </picture> + +</p> +<p>这是当前的博客架构,文件保存在<code>Github</code>仓库,通过<code>Cloudflare Page</code>提供访问。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="slow.png" width="1842" height="531" alt="国内访问情况" title="" loading="lazy" /> + </picture> + +</p> +<p>众所周知,在国内,<code>Cloudflare</code>的CDN属于反向加速,平均耗时在1.5s左右。</p> + 背景 +

      + +博客架构 + + +

      +

      这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。

      +

      + +国内访问情况 + + +

      +

      众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。

      +

      今天,我们就来讲一下,如何实现国内海外双线路博客访问。

      +

      大体思路

      +
      海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。
      +
      +Cloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。
      +
      +国内CDN添加域名后,也会提供一个cname域名B。
      +
      +使用国内dns解析服务,配置cname双线路解析。
      +

      具体操作

      +
        +
      1. +

        Cloudflare Page添加新域名解析

        +

        这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。

        +

        + +Cloudflare Page添加域名 + + +

        +
      2. +
      3. +

        配置国内CDN

        +

        我用的腾讯云,其他服务商也是可以的。

        +

        + +添加CDN域名 + + +

        +
        加速域名:填写博客对外访问的域名
        +回源地址和Host:填写第一步新加的域名
        +

        添加成功后,会有一个cname地址,这里是国内线路解析要用到的。

        +
      4. +
      5. +

        DNS解析调整

        +

        Cloudflare不支持双线路配置,国内服务商支持,我这里用的是腾讯云。

        +

        首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到Cloudflare的邮件,不需要理会。

        +
        liudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:
        +
        +sandals.dnspod.net
        +heron.dnspod.net
        +[not set]
        +[not set]
        +[not set]
        +
        +此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。
        +

        然后添加解析,默认走国内CDN,境外走Cloudflare Page

        +

        + +DNS双线路解析 + + +

        +
      6. +
      +

      额外的问题

      +

      为了加速Google Analytics,使用Cloudflare Worker进行了反代,具体见加速Google Analytics

      +

      更改NS后,导致海外访问无法触发Cloudflare Worker了,导致没有博客统计数据了。

      +

      经过一番搜索后,发现Cloudflare Page有类似的Function功能,只需要在网站根目录下新建functions目录,添加对应文件即可。

      +

      这里以Hugo静态博客举例说明:

      +

      在根目录的static目录下,新建functions目录,新建analytics目录,添加post.js文件。

      +

      这个analytics/post.js是为了对应原有Worker的访问地址analytics/post,可自行修改。

      +

      post.js文件代码如下:

      +
      export async function onRequest(context) {
      +    try {
      +        return await postHandler(context);
      +    } catch(e) {
      +        return new Response(`${e.message}\n${e.stack}`, { status: 500 }); 
      +    }
      +}
      +
      +async function postHandler(context) {
      +    const GA_DOMAIN = 'google-analytics.com';
      +    const GA_COLLECT_PATH = 'g\/collect';
      +    const COLLECT_PATH = 'analytics/post';
      +    const DOMAIN = '这里填你博客的域名';
      +
      +    const url = context.request.url;
      +    const cf_ip = context.request.headers.get('CF-Connecting-IP');
      +    const cf_country = context.request.cf.country;
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`)
      +    const newReq = await readRequest(context.request, ga_url);
      +    context.waitUntil(fetch(newReq));
      +
      +    return new Response(null, {
      +        status: 204,
      +        statusText: 'No Content',
      +      });
      +}
      +
      +async function readRequest(request, url) {
      +    const { _, headers } = request;
      +    const nq = {
      +      method: request.method,
      +      headers: {
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: request.body,
      +    };
      +    return new Request(url, nq);
      +}
      +

      优化效果

      +

      + +优化后的访问 + + +

      +

      有了国内CDN的加持,平均耗时优化到1s左右了。

      +]]>
      +
      + + 2023年终总结 + https://liudon.com/posts/review-2023/ + Thu, 04 Jan 2024 18:41:20 +0800 + https://liudon.com/posts/review-2023/ + <p>2023年过完了,是时候来个总结了。</p> +<h4 id="博客">博客</h4> +<p>2023年一共更新了15篇内容,共计12000字。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/review-2023/20240104184720_hu9917978577706645891.webp 733w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/review-2023/20240104184720_hu6551399417996237227.png 733w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20240104184720.png" width="733" height="436" alt="Google Analytics全年统计" title="" loading="lazy" /> + </picture> + +</p> +<p>访问Top3的文章:</p> + 2023年过完了,是时候来个总结了。

      +

      博客

      +

      2023年一共更新了15篇内容,共计12000字。

      +

      + +Google Analytics全年统计 + + +

      +

      访问Top3的文章:

      +

      将博客部署到星际文件系统(IPFS)

      +

      利用Github Actions定时抓取微博

      +

      优化博客的累计布局偏移(CLS)问题

      +

      主要是因为有在v2ex发帖导流,所以访问量高一些。

      +

      2023年12月北京暴雪记录

      +

      没想到的是一篇暴雪记录,收获了最多的评论,可能大家更容易共情。

      +

      不过从侧面也说明了技术的东西并没有太多人看,所以后来就不再分享导流了。

      +

      工作

      +

      今年搬到了后厂村,见识了互联网的人流。

      +

      在23年最后一个工作日,下班路上,算了一下,这一年晚上9点半之后打车59次。

      +

      而且年底这段时间,打车愈发困难,至少要排队1个小时。

      +

      相比之前一坐坐一天,每天中午会绕公司大楼转两圈。

      +

      + +23年步行统计 + + +

      +

      驾照

      +

      驾照终于到手了

      +

      趁着娃暑假、十一假期,开车上路了。

      +

      + +第一次开车上路 + + +

      +

      参与了第一次摇号。

      +

      + + + + +

      +

      换了新手机

      +

      用了5年的iPhone7 plus,过地铁NFC时不时刷不开了,感觉得换手机了。

      +

      十一假期回来在官网订了15pro,需要11月21日才能发货。

      +

      订货后,老手机的问题越来越多,换手机变的急迫起来。

      +

      于是,开始刷实体店取货。

      +

      用了探火app监控,10月11日中午抢到一台王府井取货。

      +

      10月12日晚上8点出发,上16号线地铁后,老手机开始持续发烫,过一会自动黑屏了。

      +

      按键有触感,但是屏幕不亮,怎么捣鼓也不行,手机越来越烫,都有点怕它炸了。

      +

      换乘8号线的路上,试了试按住所有按键,手机重启了,看见苹果logo那一刻真好。

      +

      重启完总算正常了,进店排队取货,提前了一个月拿到了新手机。

      +

      + +王府井Apple Store + + + + + +取完手机 + + +

      +

      升级体验非常好,使用丝滑,再也不用插着充电器玩手机了,感谢老婆。

      +

      + +大风 + + + + + +公司大楼 + + +

      +

      + +枫叶 + + + + + +秋 + + + + + +冬 + + + + + +冬 + + +

      +

      + +秋天的百望山 + + + + + +冬天的百望山 + + + + + +冬天的百望山 + + +

      +

      财务

      +

      提前还了两笔房贷,希望明年可以把商贷还完。

      +

      股市收益率-1.11%,港股套牢中,美股稍微回了点血。

      +

      + +股市收益 + + +

      +

      本命年了,希望一切顺利。

      +

      祝大家新年快乐!

      +]]>
      +
      + + 2023年12月北京暴雪记录 + https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/ + Sat, 16 Dec 2023 10:15:33 +0800 + https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/ + <blockquote> +<p>记录暴雪下普通打工人的生活。</p> +</blockquote> +<h4 id="12月14日-周四">12月14日 周四</h4> +<p>北京的雪已经连着下了两天了。</p> +<p>12月11日,也是因为下雪,晚上打车打到10点半才叫到车。</p> +<p>所以这次下雪后,晚上就早走了。</p> + +

      记录暴雪下普通打工人的生活。

      + +

      12月14日 周四

      +

      北京的雪已经连着下了两天了。

      +

      12月11日,也是因为下雪,晚上打车打到10点半才叫到车。

      +

      所以这次下雪后,晚上就早走了。

      +

      7:15坐上公司摆渡车,7:40左右到西二旗。

      +

      下车前刷微博看到有说昌平线故障,还没细看就到站下车了,还没想到事情的严重性。

      +

      路口已经有交警在指挥交通,看见两辆消防车在等红灯,事后想可能是去救援的。

      +

      走到进站口,就听到有人喊,“昌平线故障不停车,请更换其他交通工具”。

      +

      我坐13号线,继续往前走,就看到了下面的情景。

      +

      + +西二旗进站口 + + +

      +

      栏杆里全是人,后面的人还在不断的往里进。

      +

      看了下门口的人不动,人太多,不敢进去,先看看情况。

      +

      过了一会,有人喊13号线也停了,扭头赶紧走。

      +

      + +打车等候人数 + + +

      +

      第一反应打车,看了下,排队900人,直接放弃,换公交。

      +

      最近的公交站在西二旗大街,走吧。

      +

      到公交站一看,人更多,本来路就窄,全是人,走都没法走。

      +

      公交也没戏了,继续走吧,走过这段到前面看看再。

      +

      事后看说有的车被挤爆了,吓的别的车都不敢开门了。

      +

      走的有点冷,闻见一股香味,一看手机快8点半了,找个地吃点东西先。

      +

      + +吉祥馄饨 + + +

      +

      一碗馄饨下肚,暖和多了,继续上路。

      +

      + +路上 + + +

      +

      走的小腿有点酸了,终于到公交站了。

      +

      等了一小会,总算上车了。

      +

      车也不敢开的快,10迈左右。

      +

      晚上10点,终于到家了。

      +

      路上花费2小时45分钟

      +

      12月15日 周五

      +

      早上坐公交到地铁站。

      +
      + +
      +

      霍营地铁站盛况,人挤满了整个通道。

      +

      立马出站,换乘公交。

      +

      路上花费2小时

      +]]>
      +
      + + 使用Hugo实现响应式和优化的图片 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + Sun, 10 Dec 2023 08:29:05 +0800 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + <p>继续我们的<a href="./tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/">博客优化之旅</a>,本篇内容我们将介绍如何使用<code>Hugo</code>实现响应式和优化的图片。</p> +<h4 id="问题">问题</h4> +<p>在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>。</p> +<p>经过一段运行后,发现这里有一个弊端。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Run hugo --gc --minify --cleanDestinationDir +</span></span><span style="display:flex;"><span>Start building sites … +</span></span><span style="display:flex;"><span>hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer +</span></span></code></pre></div><p>随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。</p> + 继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

      +

      问题

      +

      在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

      +

      经过一段运行后,发现这里有一个弊端。

      +
      Run hugo --gc --minify --cleanDestinationDir
      +Start building sites … 
      +hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio
      +
      +ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
      +ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer
      +

      随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

      +

      失败的时候,需要手动重跑构建,自动化发布卡壳了。

      +

      优化

      +

      经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

      +
      +

      Image processing

      +

      Resize, crop, rotate, filter, and convert images.

      +

      https://gohugo.io/content-management/image-processing/

      +
      +

      下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

      +

      image processing需要用到Page bundles,所以文章目录结构需要调整, +将一篇文章的资源(md文件,图片等)放在一个目录下。

      +
      content/
      +├── posts
      +│   ├── my-post
      +│   │   ├── content1.md
      +│   │   ├── content2.md
      +│   │   ├── image1.jpg
      +│   │   ├── image2.png
      +│   │   └── index.md
      +│   └── my-other-post
      +│       └── index.md
      +

      目录结构调整完毕后,接下来修改图片显示文件代码。

      +

      这里需要生成webp格式图片,所以需要使用hugo的extended版本

      +

      PagerMod主题涉及到图片显示的一共三个文件:

      +
        +
      • +

        _default/_markup/render-image.html,对应markdown图片语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +{{ with $src := .Page.Resources.GetMatch .Destination }}
        +    {{- if $responsiveImages -}}
        +        <picture>
        +            /*只有使用了hugo扩展版本的,才生成webp格式图片*/
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +            <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +        </picture>
        +    {{- else }}
        +        <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +    {{- end }}
        +{{ end }}
        +
      • +
      • +

        partials/cover.html,对应文章封面解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    /*封面响应式图片配置开关,默认为true*/
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- if $responsiveImages -}}
        +        <picture>
        +        {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
        +        <source type="image/webp" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +            {{- end -}}
        +        {{- end -}}" sizes="{{ $dataSzes }}" />
        +        {{- end -}}
        +        <source type="{{ $cover.MediaType.Type }}" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if ge $cover.Width . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}
        +        {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
        +
        +        <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        </picture>
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      • +
      • +

        shortcodes/figure.html,对应文章内figure语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{ $src := .Get "src" }}
        +{{ $align := .Get "align" }}
        +{{ $alt := .Get "alt" }}
        +{{ $caption := .Get "caption" }}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
        +        {{- if eq (.Get "align") "center" }}align-center {{ end }}
        +        {{- with .Get "class" }}{{ . }}{{- end }}"
        +{{- end -}}>
        +    {{- if .Get "link" -}}
        +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        +    {{- end }}
        +    {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        +    <picture>
        +        {{- if $responsiveImages -}}
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +        {{- end }}
        +        <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
        +        {{- if or ($alt) ($caption) }}
        +        alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
        +        {{- end -}}
        +        {{- with $src.Width -}} width="{{ . }}"{{- end -}}
        +        {{- with $src.Height -}} height="{{ . }}"{{- end -}}
        +        /> <!-- Closing img tag -->
        +    </picture>
        +    {{ end }}
        +    {{- if .Get "link" }}</a>{{ end -}}
        +    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        +        <figcaption>
        +            {{ with (.Get "title") -}}
        +                {{ . }}
        +            {{- end -}}
        +            {{- if or (.Get "caption") (.Get "attr") -}}<p>
        +                {{- .Get "caption" | markdownify -}}
        +                {{- with .Get "attrlink" }}
        +                    <a href="{{ . }}">
        +                {{- end -}}
        +                {{- .Get "attr" | markdownify -}}
        +                {{- if .Get "attrlink" }}</a>{{ end }}</p>
        +            {{- end }}
        +        </figcaption>
        +    {{- end }}
        +</figure>
        +
      • +
      +

      使用效果

      +

      正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

      +
        +
      • markdown图片显示
      • +
      +

      + +render-image + + +

      +
        +
      • figure短代码显示
      • +
      +

      + +figure + + +

      +
        +
      • 封面显示
      • +
      +

      + +cover + + +

      +

      提示:

      +

      随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

      +
      Start building sites  
      +hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
      +Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
      +Built in 22356 ms
      +

      可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

      +
      buildDrafts: false
      +buildFuture: false
      +buildExpired: false
      +
      +timeout: 60s // 调大此处的时间即可
      +

      终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。

      +]]>
      +
      + + 加速Google Analytics + https://liudon.com/posts/optimize-google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/posts/optimize-google-analytics/ + <h3 id="起因">起因</h3> +<p><code>Google Analytics</code>是一款优秀的流量分析服务,集成方便,使用简单。</p> +<p>最近在优化页面访问速度,发现<code>Google Analytics</code>是一个优化点。</p> +<h3 id="优化">优化</h3> +<h4 id="1-访问加速">1. 访问加速</h4> +<p>国内访问<code>Google Analytics</code>很慢,同时还面临着各种广告屏蔽插件拦截。</p> + 起因 +

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      +

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      +

      优化

      +

      1. 访问加速

      +

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      +

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      +

      Cloudflare新建Worker,代码如下,保存后部署。

      +
      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      +
      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      +

      通过性能分析,发现js文件过大,影响页面加载速度。

      +

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      +

      搜索一番,找到一个瘦身版Google Analytics

      +
      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      +

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      +

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      +
      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      +

      + + + + +

      +]]>
      +
      + + 使用Google Indexing API加速博客收录 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + <p>对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。</p> +<p>今天,我们就来介绍一种利用<code>Google Indexing API</code>接口,通过<code>Github Actions</code>实现部署时通知<code>Google</code>抓取页面内容。</p> + 对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      +

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      +

      操作步骤:

      +
        +
      1. +

        申请Google API凭证

        +

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        +

        + + + + +

        +

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        +

        + + + + +

        +
        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        +

        + + + + +

        +

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        +

        点击后面的三个点按钮,选择管理密钥。

        +

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        +

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

        +
      2. +
      3. +

        将服务账号关联到Google Search Console

        +

        进入Google Search Console控制台,选择你的网站。

        +

        找到设置里的用户和权限,点击添加用户。

        +

        + + + + +

        +
        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      4. +
      5. +

        配置Github Action

        +
          +
        • +

          添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

          +
        • +
        • +

          编辑workflow编排任务,新增步骤

          +
        • +
        +
        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

        + + + + +

        +
      6. +
      +]]>
      +
      + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      + + 利用Github Actions定时抓取微博 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + Sat, 07 Oct 2023 13:17:57 +0800 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + <h4 id="背景">背景</h4> +<p>在微博上关注了一些用户,比如<a href="https://weibo.com/u/1401527553">tk教主</a>,<a href="https://weibo.com/u/1670659923">月风</a>。</p> +<p>但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。</p> +<h4 id="实现方案">实现方案</h4> +<p><strong>整体思路:利用<code>Github Actions</code>的<code>Scheduled</code>任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。</strong></p> + 背景 +

      在微博上关注了一些用户,比如tk教主月风

      +

      但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。

      +

      实现方案

      +

      整体思路:利用Github ActionsScheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。

      +
        +
      1. +

        新建仓库,比如weibo_archive

        +
      2. +
      3. +

        添加抓取脚本,完整代码

        +

        这里用到微博两个接口:

        +
        // 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断
        +https://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom&type=uid&value=$uid&containerid=107603$uid
        +
        +// 根据微博id,抓取微博完整的内容
        +https://m.weibo.cn/statuses/extend?id=$id
        +
      4. +
      5. +

        添加环境变量。

        +
          +
        • +

          进入个人设置->Developer Settings->Personal access tokens->Tokens (classic),创建新的Token,记下对应的值。

          +
        • +
        • +

          进入第一步创建仓库的配置页,点击Secrets and variables下的Actions

          +

          切到Secret目录,创建新的Secret变量,名称为TOKEN,值为前一步记录的值;切到Variables目录,创建新的Variables变量,名称为WEIBO_UIDS,值为你需要抓取的微博用户id,多个用户的话以|分割。

          +
        • +
        +
      6. +
      7. +

        添加定时任务,完整yaml文件如下。

        +
        # This is a basic workflow to help you get started with Actions
        +
        +name: CI
        +
        +# Controls when the workflow will run
        +on:
        +# Triggers the workflow on push or pull request events but only for the "main" branch
        +push:
        +    branches: [ "main" ]
        +pull_request:
        +    branches: [ "main" ]
        +
        +# Allows you to run this workflow manually from the Actions tab
        +workflow_dispatch:
        +schedule:
        +    - cron: '*/5 * * * *'
        +
        +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
        +jobs:
        +# This workflow contains a single job called "build"
        +build:
        +    # The type of runner that the job will run on
        +    runs-on: ubuntu-latest
        +
        +    permissions:      
        +    contents: write
        +
        +    # Steps represent a sequence of tasks that will be executed as part of the job
        +    steps:
        +    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
        +    - uses: actions/checkout@v3
        +
        +    # Runs a single command using the runners shell
        +    - name: archive weibo
        +        run: |
        +        chmod +x ./weibo_archive.sh
        +        ./weibo_archive.sh
        +        shell: bash
        +        env:
        +        weibo_uids: ${{ vars.weibo_uids }}
        +
        +    # Runs a set of commands using the runners shell
        +    - name: Commit changes
        +        uses: EndBug/add-and-commit@v9
        +        env:
        +        github_token: ${{ secrets.TOKEN }}
        +        add: .
        +
      8. +
      +

      效果

      +

      抓取后的内容,会按用户id分别保存到不同文件。

      +

      + +效果 + + +

      +

      不过这个方案有一个唯一的缺点,Github Actions定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。

      +
      +

      Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.

      +
      +]]>
      +
      + + 北大口腔牙周刮治记录 + https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/ + Sun, 17 Sep 2023 15:46:32 +0800 + https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/ + <h4 id="病情">病情</h4> +<p>上次洗完牙后,还是不时有出血的情况。</p> +<p>前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。</p> +<p>于是,又跑到医院来看牙了。</p> +<p>医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。</p> + 病情 +

      上次洗完牙后,还是不时有出血的情况。

      +

      前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。

      +

      于是,又跑到医院来看牙了。

      +

      医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。

      +

      回来查了下,牙周刮治还是挺痛苦的,想着找个专业医院看看再。

      +

      挂号

      +

      于是开始北大口腔挂号,中间挂了一次总院的普通号,但是网上好多说是规培生操作,又取消了。

      +

      找了农行的代挂号服务,北大口腔只能微信预约,他们也没渠道,只能自己挂了。

      +

      总院实在是挂不到,于是转战第一门诊部的号。

      +

      功夫不负有心人,7月5日总算挂上了牙周科李菲医生的号。

      +

      + + +

      +

      治疗

      +

      7月7日,第一次看医生,刮治是没的跑了。

      +

      + + +

      +

      做了牙齿探针,每个牙都要测一下,略疼。

      +

      开了治疗计划,刮治分两次,每次半口牙,总费用3000元左右。

      +

      第一次只洗牙,确实是专业,洗牙不到半个小时搞定。

      +

      最关键的,洗完后我刷牙确实很少再出血了。

      +

      8月3日,第一次刮治,只做了右半边。

      +

      医生说我这算是轻的,刮治比洗牙会疼一些,可以打麻药也可以不打。

      +

      行吧,那咱就不打麻药,来吧。

      +

      医生开始操作,感觉和洗牙是一样的,也是拿喷水的机器在牙上弄来弄去。

      +

      但是比洗牙更深一些,感觉是直接到牙龈下面了,确实要更疼一些。

      +

      洗完后,再拿一个铁丝一样的东西在牙上使劲刮来刮去。

      +

      自我感觉,还是前面洗牙更痛苦一些,后面刮来刮去没什么感觉。

      + + + + + +
      +
      + 铁丝一样的工具,第二次刮治拍的 +
      +
      + +

      半个小时搞定,本来我以为我这得一俩小时,医生说那得是非常严重的,你这属于轻的。

      +

      9月2日,第二次刮治,做了左半边。

      +

      左边牙龈问题更压重些,这次痛感更强烈一些。

      +

      同样也是半个小时搞定,1个月后再来复诊。

      +

      费用

      +
      洗牙:480元,单子找不到了,我记得是这个数。
      +
      +第一次刮治:1286元
      +
      +第二次刮治:1176元
      +

      总费用2942元,基本都走了医保。

      +]]>
      +
      + + 故乡回忆之旅 + https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/ + Sat, 09 Sep 2023 23:19:03 +0800 + https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/ + <p>赶在8月底,趁着娃暑假的尾声,回了趟老家。</p> +<p>老家有条俗语,“永福庄的街,三里长”。</p> +<p>这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。</p> +<p>小时候,整天在这条街上跑来跑去。</p> + 赶在8月底,趁着娃暑假的尾声,回了趟老家。

      +

      老家有条俗语,“永福庄的街,三里长”。

      +

      这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。

      +

      小时候,整天在这条街上跑来跑去。

      +

      我们那巷子基本很少是堵头的,你可以穿到任意一个巷子,最后还能回到大街。

      +

      小时候的大街,感觉很宽很长。

      +

      现在大街都垫高了,大街也变的窄了许多。

      +

      大街上的人很少,好多店铺都关门了。

      +

      走在大街上,小时候的一幕幕映在眼前。

      +

      有的地方,已经拆了重盖;

      +

      有的地方,已经人去楼空。

      +

      + + + + +

      + + + + +
      + 爷爷待过的大队部 +
      +
      + + + + + + +
      + + + + +
      + 以前的供销社 +
      +
      +

      +

      + + + + +

      + + + + +
      + 老爷爷家 +
      +
      + + + + + + +
      + + + + +
      + 姥爷以前的药铺 +
      +
      + + + + + + +
      + + + + +
      + 姥姥家 +
      +
      +

      +

      + + + + +

      + + + + +
      + 最早是肉食店,后来开过药铺 +
      +
      + + + + + + +
      + + + + +
      + 里面以前是个学校 +
      +
      +

      +

      + + + + +

      + + + + +
      + 巷口的大饭店 +
      +
      + + + + + + +
      + + + + +
      + 一栋风格怪异的楼 +
      +
      + + + + + + +
      + + + + +
      + 巷子 +
      +
      +

      +

      + + + + +

      + + + + +
      + 刘家祖庙 +
      +
      + + + + + + +
      + + + + +
      + 赵家祖庙 +
      +
      +

      +

      + + + + +

      + + + + +
      + 赵家祖庙 +
      +
      + + + + + + +
      + + + + +
      + 关帝庙 +
      +
      +

      +]]>
      +
      + + 解决Golang使用go get安装包后找不到可执行文件的问题 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + Thu, 17 Aug 2023 09:20:50 +0800 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + <h4 id="背景">背景</h4> +<p>编译流水线代码</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go get google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build -o xxx +</span></span></code></pre></div><p>在go升级到1.20.1版本后,执行报错。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protoc-gen-go: program not found or is not executable +</span></span></code></pre></div><h4 id="解决">解决</h4> +<blockquote> +<p>Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.</p> +<p>In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.</p> + 背景 +

      编译流水线代码

      +
      go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +
      +protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto
      +
      +...
      +
      +go build -o xxx
      +

      在go升级到1.20.1版本后,执行报错。

      +
      protoc-gen-go: program not found or is not executable
      +

      解决

      +
      +

      Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.

      +

      In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.

      +

      https://docs.studygolang.com/doc/go-get-install-deprecation

      +
      +

      从 Go 1.7 版本开始,go get 命令默认只会下载包,不会自动编译和安装可执行文件。

      +

      因此,如果你想要使用 go get 命令安装包并编译可执行文件,你需要使用 go install 命令。

      +

      替换为go install解决。

      +]]>
      +
      + + 修正Hugo的JSON Feed格式 + https://liudon.com/posts/fix-hugo-json-feed/ + Sat, 25 Mar 2023 14:11:18 +0800 + https://liudon.com/posts/fix-hugo-json-feed/ + <h4 id="问题背景">问题背景</h4> +<p>前几天在<a href="https://planetics.xyz/">Planet</a>里follow自己的<a href="https://liudon.eth">web3博客</a>,遇到下面的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp 716w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png 716w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202303251415675.png" width="716" height="544" alt="PlanetFeedError" title="" loading="lazy" /> + </picture> + +</p> +<p>经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。</p> +<p>因为我的已经修正没法截图,这里以<a href="https://dvel.me/index.json">dvel的博客</a>举例,格式类似如下。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[ +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> &#34;content&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&amp;#34;}], &amp;#34;temperature&amp;#34;: 0.7 }&amp;#39; ``` 响应格式为: ``` { &amp;#34;id&amp;#34;:&amp;#34;chatcmpl-abc123&amp;#34;, &amp;#34;object&amp;#34;:&amp;#34;chat.completion&amp;#34;, &amp;#34;created&amp;#34;:1677858242, &amp;#34;model&amp;#34;:&amp;#34;gpt-3.5-turbo-0301&amp;#34;, &amp;#34;usage&amp;#34;:{ &amp;#34;prompt_tokens&amp;#34;:13, &amp;#34;completion_tokens&amp;#34;:7, &amp;#34;total_tokens&amp;#34;:20 }, &amp;#34;choices&amp;#34;:[ { &amp;#34;message&amp;#34;:{ &amp;#34;role&amp;#34;:&amp;#34;assistant&amp;#34;, &amp;#34;content&amp;#34;:&amp;#34;\\n\\nThis is a test!&amp;#34; }, &amp;#34;finish_reason&amp;#34;:&amp;#34;stop&amp;#34;, &amp;#34;index&amp;#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n&#34;, +</span></span><span style="display:flex;"><span> &#34;permalink&#34;: &#34;https://dvel.me/posts/chd-quiz-answer/&#34;, +</span></span><span style="display:flex;"><span> &#34;summary&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&#34;, +</span></span><span style="display:flex;"><span> &#34;title&#34;: &#34;CHD 油猴脚本:每日签到自动答题&#34; +</span></span><span style="display:flex;"><span> }, +</span></span><span style="display:flex;"><span> ... +</span></span><span style="display:flex;"><span>] +</span></span></code></pre></div><p>下面是一个<code>JSON Feed</code>的示例,详细规范见<a href="https://www.jsonfeed.org/">jsonfeed.org</a>。</p> + 问题背景 +

      前几天在Planet里follow自己的web3博客,遇到下面的错误。

      +

      + +PlanetFeedError + + +

      +

      经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。

      +

      因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。

      +
      [
      +  {
      +    "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n",
      +    "permalink": "https://dvel.me/posts/chd-quiz-answer/",
      +    "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!",
      +    "title": "CHD 油猴脚本:每日签到自动答题"
      +  },
      +  ...
      +]
      +

      下面是一个JSON Feed的示例,详细规范见jsonfeed.org

      +
      {
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": "My Example Feed",
      +    "home_page_url": "https://example.org/",
      +    "feed_url": "https://example.org/feed.json",
      +    "items": [
      +        {
      +            "id": "2",
      +            "content_text": "This is a second item.",
      +            "url": "https://example.org/second-item"
      +        },
      +        {
      +            "id": "1",
      +            "content_html": "<p>Hello, world!</p>",
      +            "url": "https://example.org/initial-post"
      +        }
      +    ]
      +}
      +

      修复方案

      +
        +
      1. 添加自定义jsonfeed模版文件,路径为layouts/_default/index.jsonfeed.json
      2. +
      +

      文件内容如下:

      +
      {{- $pctx := . -}}
      +{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}
      +{{- $pages := slice -}}
      +{{- if or $.IsHome $.IsSection -}}
      +{{- $pages = $pctx.RegularPages -}}
      +{{- else -}}
      +{{- $pages = $pctx.Pages -}}
      +{{- end -}}
      +{{- $limit := site.Config.Services.RSS.Limit -}}
      +{{- if ge $limit 1 -}}
      +{{- $pages = $pages | first $limit -}}
      +{{- end -}}
      +{{- $title := "" }}
      +{{- if eq .Title .Site.Title }}
      +{{- $title = .Site.Title }}
      +{{- else }}
      +{{- with .Title }}
      +{{- $title = print . " on "}}
      +{{- end }}
      +{{- $title = print $title .Site.Title }}
      +{{- end }}
      +{
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": {{ $title | jsonify }},
      +    "home_page_url": {{ .Permalink | jsonify }},
      +    {{- with  .OutputFormats.Get "jsonfeed" }}
      +    "feed_url": {{ .Permalink | jsonify  }},
      +    {{- end }}
      +    {{- if (or .Site.Params.author .Site.Params.author_url) }}
      +    "authors": [{
      +      {{- if .Site.Params.author }}
      +        "name": {{ .Site.Params.author | jsonify }},
      +      {{- end }}
      +      {{- if .Site.Params.author_url }}
      +        "url": {{ .Site.Params.author_url | jsonify }}
      +      {{- end }}
      +    }],
      +    {{- end }}
      +    {{- if $pages }}
      +    "items": [
      +        {{- range $index, $element := $pages }}
      +        {{- with $element }}
      +        {{- if $index }},{{end}} {
      +            "title": {{ .Title | jsonify }},
      +            "id": {{ .Permalink | jsonify }},
      +            "url": {{ .Permalink | jsonify }},
      +            {{- if .Site.Params.showFullTextinJSONFeed }}
      +            "summary": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            "content_html": {{ .Content | jsonify }},
      +            {{- else }}
      +            "content_text": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            {{- end }}
      +            {{- if .Params.cover.image }}
      +            {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
      +            {{- if $cover }}
      +            "image": {{ (path.Join .RelPermalink $cover) | absURL | jsonify }},
      +            {{- end }}
      +            {{- end }}
      +            "date_published": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
      +            {{- $tags := slice -}}
      +            {{ with .Params.tags }}
      +            {{ range . }}
      +            {{ $tags = $tags | append (. | jsonify) }}
      +            {{end}}
      +            {{end}}
      +            "tags": [{{ delimit $tags ", " }}]
      +        }
      +        {{- end }}
      +        {{- end }}
      +    ]
      +    {{ end }}
      +}
      +
        +
      1. 开启JSON Feed
      2. +
      +

      配置文件调整如下:

      +
      outputFormats:
      +  jsonfeed: # 添加jsonfeed输出格式
      +    mediaType: application/feed+json
      +    baseName: feed
      +    rel: alternate
      +    isPlainText: true
      +
      +outputs:
      +    home:
      +        - HTML
      +        - RSS
      +        - json # fusejs搜索依赖index.json,不要漏掉这个配置
      +        - jsonfeed # 开启jsonfeed
      +
      +params:
      +    ...
      +
      +    showFullTextinJSONFeed: true # jsonfeed开启全文输出
      +

      参考资料:How to add JSON Feed support to Hugo

      +]]>
      +
      + + 我的学车之路 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + Fri, 24 Mar 2023 20:48:24 +0800 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + <p>之前在<a href="https://liudon.com/posts/review-2022/">2022年终总结</a>提到过,我在练车考驾照。</p> +<p>就在昨天,终于拿证了。👏👏👏</p> +<p>咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月12日,科目一考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月22日,科目二模拟驾驶。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月13日,科目二第一次上车练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月4日,年后驾校恢复培训,继续科目二练车。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月13日,科目二考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月11日,科目三上路练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月23日,上午科目三考试通过,下午科目四考试通过。 +</span></span><span style="display:flex;"><span>考试的时候,早上遇到临时交通管制,一直到9点40才开考。 +</span></span><span style="display:flex;"><span>考完回来,班车上的人说又管制不能考了。 +</span></span><span style="display:flex;"><span>班车拉回驾校,剩下的人中午加班考试。 +</span></span></code></pre></div><p>终于不用再5点半起床赶班车了。🥱</p> + 之前在2022年终总结提到过,我在练车考驾照。

      +

      就在昨天,终于拿证了。👏👏👏

      +

      咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶

      +
      2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。
      +
      +2022年10月12日,科目一考试通过。
      +
      +2022年10月22日,科目二模拟驾驶。
      +
      +2022年11月13日,科目二第一次上车练习。
      +
      +2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。
      +
      +2023年2月4日,年后驾校恢复培训,继续科目二练车。
      +
      +2023年2月13日,科目二考试通过。
      +
      +2023年3月11日,科目三上路练习。
      +
      +2023年3月23日,上午科目三考试通过,下午科目四考试通过。
      +考试的时候,早上遇到临时交通管制,一直到9点40才开考。
      +考完回来,班车上的人说又管制不能考了。
      +班车拉回驾校,剩下的人中午加班考试。
      +

      终于不用再5点半起床赶班车了。🥱

      +

      + +本本到手啦 + + +

      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      + + 新冠疫情后的第一个春节 + https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/ + Thu, 16 Feb 2023 20:56:38 +0800 + https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/ + <blockquote> +<p>下面的内容是由chatGPT润色生成的。</p> +<p>AI太强大了 😂</p> +</blockquote> +<p>当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。</p> +<p>但我从未想过,等我长大后,我也会成为其中的一员。</p> + +

      下面的内容是由chatGPT润色生成的。

      +

      AI太强大了 😂

      + +

      当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。

      +

      但我从未想过,等我长大后,我也会成为其中的一员。

      +

      由于疫情的影响,我已经连续三年不能回家过年了。

      +

      每次我告诉父母我无法回家,他们总是表现得非常平静,但我不知道他们挂了电话后的心情会如何。

      +

      直到今年,我们全家都经历了一次感染,但这也使我们有了机会在这个特殊的春节回家过年。

      +

      提前请了假,带娃体验下老家的生活。

      +

      + +回家 + + +

      +

      回家啦。

      +

      + +赶集 + + +

      +

      赶大集。

      +

      + + +

      +

      烧火。

      +

      + +放烟花 + + +

      +

      放烟花。

      +

      + +抓鸟 + + +

      +

      抓鸟。

      +

      + +放孔明灯 + + +

      +

      放孔明灯。

      +

      + +蹭饭 + + +

      +

      邻居家蹭饭。

      +]]>
      +
      + + 第一次清理键盘 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + Mon, 16 Jan 2023 14:43:38 +0800 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + <p>19年生日的时候,媳妇送了一款flico的机械键盘。</p> +<p>这次搬家后,想着年前清理下键盘,实在是太脏了。</p> +<p>周五下班,带上键盘回家。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202301161450523.jpeg" width="1706" height="1279" alt="" title="" loading="lazy" /> + </picture> + +</p> + 19年生日的时候,媳妇送了一款flico的机械键盘。

      +

      这次搬家后,想着年前清理下键盘,实在是太脏了。

      +

      周五下班,带上键盘回家。

      +

      + + + + +

      +

      键盘全貌,上面好多油。

      +

      + + + + +

      +

      开拆。

      +

      + + + + +

      +

      手还是太慢,上工具吧。

      +

      + + + + +

      +

      全部拆完。

      +

      + + + + +

      +

      内部特写。

      +

      + + + + +

      +

      清理出来的灰屑、头发,这键盘见证了我的发迹线变化

      +

      + + + + +

      +

      终于清理干净了。

      +

      + + + + +

      +

      复原,又可以咔咔写代码了。

      +]]>
      +
      + + 2022年终总结 + https://liudon.com/posts/review-2022/ + Thu, 12 Jan 2023 07:21:20 +0800 + https://liudon.com/posts/review-2022/ + <p>2022年已经过去1周多了,记录一下我的2022年。</p> +<h4 id="疫情">疫情</h4> +<p>2022年,是新冠疫情的第三年,也是切身感受到的一年。</p> +<p>3月22日晚,8点半和同事刚上13号线地铁。</p> + 2022年已经过去1周多了,记录一下我的2022年。

      +

      疫情

      +

      2022年,是新冠疫情的第三年,也是切身感受到的一年。

      +

      3月22日晚,8点半和同事刚上13号线地铁。

      +

      紧接着看到群里有人说,公司大厦因为疫情封控了,只进不出。

      +

      + +大厦封控 + + +

      +

      第一次感受弹窗3,居家隔离。

      +

      + +弹窗3 + + +

      +

      + +居家隔离 + + +

      +

      + +居家观察 + + +

      +

      5月21日,开启居家办公。

      +

      6月5日,开始到岗工作。

      +

      11月17日,公司通知第二天居家办公。

      +

      11月21日,小区通知临时管控。

      +

      + +小区封控 + + +

      +

      12月10日,媳妇中招。

      +

      12月12日,自己中招。

      +

      12月18日,娃中招。

      +

      + + +

      +

      12月20日,开始到岗工作,持续了近一个月的居家隔离生活终于结束。

      +

      老妈冒着北京疫情高峰感染的风险,过来帮我们带娃。

      +

      从1周一检,到3天一检,不知道健康宝有没有年终报告,告诉你今年做了多少次核酸,相信会是很棒的一个数字。

      +

      入学

      +

      上半年赶着疫情的间隙,整理好了娃的入学资料。

      +

      经过一个月的焦虑等待后,最终被附近的学校录取,也确实感受到了离家近的好处。

      +

      详细经过见记录2022年海淀幼升小

      +

      休假

      +

      春节没有回家过年,上半年北京和老家交替出现疫情,最终在8月份休假回了趟家。

      +

      驾照

      +

      因为疫情,感觉还是得考个驾照,拖延了N年的事项提上了日程。

      +

      6月份报名,一直拖到10月份才过的科目一。

      +

      11月底开始摸车了,刚上2节课因为疫情封校了,现在学的都快忘光了。

      +

      搬家

      +

      年底公司通知搬家,又搬回了银科,兜兜转转,又回到了起点。

      +

      + +又回银科 + + +

      +

      博客

      +

      2022年,把博客又捡了回来,今年多更新吧。

      +

      + + + + +

      +

      或许是上了年纪,2022年发现泪点变得很低,健康、家人才是最重要的。

      +

      新的一年,继续开源节流,做好防护,保护好家人。

      +

      最后,祝大家新年快乐!

      +]]>
      +
      + + 去掉Cloudflare烦人的email-decode.min.js请求 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + Fri, 26 Aug 2022 23:25:57 +0800 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + <p>通过WebPageTest页面测试,发现一个<code>/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js</code>的文件请求,影响到了页面渲染。</p> + 通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。

      +

      + +webpagetest测试结果 + + +

      +

      看路径像是Cloudflare的文件,搜了下主题代码,没找到相关文件。

      +

      经过一番搜索,原来这个是Cloudflare的电子邮件地址混淆技术功能。

      +

      + +Scrape Shield + + +

      +

      关闭这个功能即可。

      +]]>
      +
      + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="./posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 优化博客的累计布局偏移(CLS)问题 + https://liudon.com/posts/fix-blog-cls/ + Sat, 20 Aug 2022 07:27:22 +0800 + https://liudon.com/posts/fix-blog-cls/ + <p>此文已过期,优化方案参考<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>.</p> +<h4 id="问题表现">问题表现</h4> +<p>7月份将博客部署由<code>Github</code>迁移到<code>Cloudflare</code>后,开始关注博客的性能问题。</p> +<p>偶然看到苏卡卡大佬的<a href="https://blog.skk.moe/post/fix-blog-cls/">CLS优化文章</a>,拿自己博客也测试了下,发现也存在同样的问题。</p> + 此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      +

      问题表现

      +

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      +

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      +

      + +Lighthouse测试报告 + + +

      +

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      +

      为了解决这个问题,需要指定封面的宽高参数。

      +

      + +cover.html code + + +

      +

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      +

      解决方案

      +
        +
      1. +

        新增封面配置

        +

        文章封面配置新增widthheight属性。

        +
        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. +
      3. +

        自定义封面文件

        +

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

        +
            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        +

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        +

        + +cos-img-process + + +

        +
      4. +
      5. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      6. +
      +

      再进一步

      +

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      +

      基于markdown语法的图片代码,是不支持宽高参数的。

      +

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      +

      + +figure.html code + + +

      +

      我们使用figure语法插入图片,指定图片宽高。

      +

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

      +
          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      +
      +

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      +
      +

      + +gtmetrix-result + + +

      +

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      +]]>
      +
      + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      + + 奥林匹克公园向日葵之旅 + https://liudon.com/posts/olympic-park-sunflower-tour/ + Thu, 21 Jul 2022 20:40:20 +0800 + https://liudon.com/posts/olympic-park-sunflower-tour/ + <p>媳妇有事回老家了,这两天自己带娃。</p> +<p>小区群里有人说奥林匹克公园的向日葵开了,适合拍照。</p> +<p>正好周六多云,没有太阳,出门遛娃。</p> +<p>带上我好久不用的相机,省得发霉了。</p> + 媳妇有事回老家了,这两天自己带娃。

      +

      小区群里有人说奥林匹克公园的向日葵开了,适合拍照。

      +

      正好周六多云,没有太阳,出门遛娃。

      +

      带上我好久不用的相机,省得发霉了。

      +

      以往都是去的南园,第一次来北园。

      +

      西门进入,沿着路往右走,一会就到。

      +

      人很多,估计大家都因为之前疫情在家憋疯了。

      +

      到了没多久,太阳又出来了,超级晒。

      +

      提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。

      +

      + +向日葵 + + + + + +向日葵 + + + + + +荷花 + + + + + +荷叶上的蜻蜓 + + +

      +]]>
      +
      + + 记第二次洗牙 + https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/ + Tue, 21 Jun 2022 21:58:49 +0800 + https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/ + <p>最近刷牙的时候,牙龈总是出血。</p> +<p>距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。</p> +<p>上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。</p> + 最近刷牙的时候,牙龈总是出血。

      +

      距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。

      +

      上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。

      +

      这次决定去医院看看,提前挂了号。

      +

      医生看过后,说得洗牙,不过当天只能先做洗牙前的检查,洗牙得预约。

      +

      开单子抽血检查,完事回家。

      +

      到了预约的日子,约的是4点,3点左右就到医院了。

      +

      15:57轮到我,进去一共20分钟完事。

      +

      我以为还有别的事项,结果医生说好了。

      +

      我说我感觉牙里卡着个东西,医生又拿起装备给我洗了一遍。

      +

      上次洗牙花了快1个小时,这也太快了。

      +

      洗牙费用380元,可以走医保报销。

      +

      以往都说医院洗牙贵,没成想外面私人的要更贵。

      +]]>
      +
      + + 记录2022年海淀幼升小 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220525202612.png" width="1920" height="2243" alt="20220525202612" title="" loading="lazy" /> + </picture> + +</p> +<blockquote> +<p>18年的热点新闻,纳税千万孩子无法在北京上学。</p> +</blockquote> +<p>一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。</p> + + +20220525202612 + + +

      +
      +

      18年的热点新闻,纳税千万孩子无法在北京上学。

      +
      +

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      +

      提前在网上搜了一番资料,都是一些机构整理的信息。

      +

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      +

      1. 信息采集

      +

      5月5日,采集系统开放。

      +

      当天下午录入相关信息,提交网上审核。

      +

      2. 网上审核

      +

      信息提交后,就开始了漫长的审核时间。

      +
      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      + +20220527202411 + + +

      +

      3. 线下审核

      +

      网上审核通过后,打印入学申请表,预约线下审核时间。

      +

      + +20220527202458 + + +

      +

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      +

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      +

      后来交流一番后,发现是自己理解错了。

      +

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      +
      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      + +街道审核 + + +

      +
        +
      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • +
      • 工作证明还需要提供满足时间要求的社保缴费记录。
      • +
      +

      4. 审核通过

      +

      5月27日,审核通过后,打印信息采集表。

      +

      + +20220527202545 + + +

      +

      5. 学校登记

      +

      6月1日对口学校发布入学登记通知书。

      +

      按通知书准备资料,到登记时间去学校交资料。

      +

      今年遇到疫情,改为线上邮件发送资料登记了。

      +

      6. 填报志愿

      +

      6月23日,海淀教育发文1911后填报志愿通知

      +

      + +20220629215840 + + +

      +

      第一志愿锁定,其他志愿自己选择填报。

      +

      6月25日锁定,不允许再修改。

      +
      +

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      +

      租房的不需要填报志愿,等待派位。

      +
      +

      7. 查看结果

      +

      + +20220629220638 + + +

      +

      6月29日15点,系统开放结果查询。

      +

      第一志愿录取,一直担心的调剂没有发生。

      +

      7月10日,收到教委短信,系统查询录取通知书。

      +

      + +20220710085749 + + +

      +

      + +20220710084342 + + +

      +

      历时1个多月的幼升小总算落地了。

      +]]>
      +
      + + Golang解析json的一个问题 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + Fri, 20 May 2022 21:18:23 +0800 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + <p>业务模块从<code>php</code>迁移到<code>golang</code>下了,最近遇到一个golang下json解析的问题:</p> +<pre><code>请求接口,按返回包字段判断请求成功与否。 +</code></pre> +<p>伪代码如下:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;encoding/json&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;fmt&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Response</span> <span style="color:#66d9ef">struct</span> { +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Code</span> <span style="color:#66d9ef">int</span> <span style="color:#e6db74">`json:&#34;code&#34;`</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Msg</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;msg&#34;`</span> +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() { +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景1,返回包符合接口要求 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;code&#34;:100,&#34;msg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析正确,符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:100 Msg:failed} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景2,返回包不符合接口要求,缺少相关字段 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> = <span style="color:#e6db74">`{&#34;retCode&#34;:100,&#34;retMsg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res1</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析错误,不符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:0 Msg:} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>} +</span></span></code></pre></div><p>这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。</p> + 业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题:

      +
      请求接口,按返回包字段判断请求成功与否。
      +
      +

      伪代码如下:

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code int    `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 场景1,返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +    // 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	// 场景2,返回包不符合接口要求,缺少相关字段
      +	str = `{"retCode":100,"retMsg":"failed"}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +    // 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +}
      +

      这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。

      +

      缺少code字段,Unmarshal解析后,按默认值处理,所以code为0,导致验证出错。

      +

      修正方案:

      +

      Code字段定义为引用类型,通过判断地址是否为nil来区分缺少字段的情况。

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code *int   `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +	// 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	if res.Code == nil || *res.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +
      +	// 返回包不符合接口要求,缺少相关字段
      +	str = `{"id":1}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +	// 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +
      +	if res1.Code == nil || *res1.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +}
      +

      参考资料:

      +

      how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided

      +

      json-key-not-set-null-golang

      +]]>
      +
      + + 疫情下的生活 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220520-192500@2x.png" width="1442" height="924" alt="20220520-192500@2x" title="" loading="lazy" /> + </picture> + +</p> +<p>不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。</p> +<p>昨天看新闻,基本没有社会面新增了,感觉要解封了。</p> +<p>没想到今天直接被打脸,封控升级了。</p> + + +20220520-192500@2x + + +

      +

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。

      +

      昨天看新闻,基本没有社会面新增了,感觉要解封了。

      +

      没想到今天直接被打脸,封控升级了。

      +

      居家办公已经快两周了,也不知道这种日子还要多久。

      +

      在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。

      +

      媳妇帮我想了个办法,投屏到电视上。

      +

      + +20220520194127 + + +

      +

      居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。

      +

      不知道这波还要多久。

      +]]>
      +
      + + 整理下博客的一些调整 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + <p>新域名上线一段时间了,通过<code>Google Search Console</code>发现了一些问题,整理下最近进行的一些调整。</p> +<ol> +<li> +<p>更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。</p> + 新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。

      +
        +
      1. +

        更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。

        +

        通过调整Github Actions命令解决:

        +
        - name: Checkout repository
        +    uses: actions/checkout@v2
        +  - name: Checkout submodules
        +    run: git submodule update --init --recursive --remote
        +
      2. +
      3. +

        修正404页面不生效的问题 +主题是自带了404.html文件的,但是部署后没有生成对应文件。

        +

        修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。

        +

        可参考文件代码

        +
      4. +
      5. +

        两个域名导致的页面权重问题 +发现有些页面liudon.xyz收录后,liudon.com就不再收录。

        +

        为了规避这种收录问题,将liudon.xyz直接301到了liudon.com上。

        +
      6. +
      +

      目前已调整完毕,观察后续收录情况。

      +]]>
      +
      + + 疫情下的五一假期 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + Thu, 05 May 2022 20:22:23 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + <p>五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。</p> +<p>当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。</p> +<p>说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。</p> + 五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。

      +

      当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。

      +

      说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。

      +

      毕竟老话说的好,手中有粮,心里不慌。

      +

      第二天出去做核酸,特意去了趟超市,想着再买点肉,结果超市也是各种采购,东西都没了,空手而归。

      +

      工作上五一前有两个大版本的功能要发布,提前1周需求才提,节前这一周忙的要死,还赶上了疫情,好在赶在发布截止日总算发出去了。

      +

      就五一当天带娃去了家旁边的公园遛了下,这次轮滑滑的不错,一直玩到晚上7点才回家。

      +

      夏天到了,北京的杨絮、柳絮各种满天飞,出门真是受罪,其余时间都在家呆着了,偶尔下楼在小区玩会。

      +

      这个假期基本上每天核酸,感觉以后要常态化核酸了。

      +

      老家在假期里突然也疫情又起,县城整个封控了,不过老爸还是回到家了,过程稍微费了点劲。

      +

      突然发现今年的疫情貌似就没消停过,去年五一假期的时候在老家烧烤,今年还不知道什么时候能回家。

      +

      这疫情什么时候是个头啊……

      +]]>
      +
      + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      + + 二刷百望山 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + Sun, 17 Apr 2022 22:57:41 +0800 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + <p>又是周末,娃约了小伙伴一起爬山。</p> +<p>百望山,二刷走起。</p> +<p>约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。</p> +<p>出门晚了,还打不到车,快10点才到。</p> +<p>小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。</p> + 又是周末,娃约了小伙伴一起爬山。

      +

      百望山,二刷走起。

      +

      约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。

      +

      出门晚了,还打不到车,快10点才到。

      +

      小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。

      +

      继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。

      +

      这次来,山上明显绿了,花开的更多了,人比上次少多了。

      +

      下山后,又一起吃了个饭,自助吃的有点撑。

      +

      今天天气真好,视野相当不错,就是太晒了。

      +

      + +俯瞰北京 + + +

      +

      + +不知名的树 + + +

      +

      + +山顶 + + +

      +]]>
      +
      + + 带娃游颐和园 + https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/ + Mon, 11 Apr 2022 00:37:55 +0800 + https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/ + <p>上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。</p> +<p>周六7点准时起床,得早点去省得人多排队。</p> +<p>8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。</p> + 上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。

      +

      周六7点准时起床,得早点去省得人多排队。

      +

      8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。

      +

      路上不堵,40多分钟到站。

      +

      进园先去码头排队,人不太多,等了有十几分钟,终于轮到了。

      +

      总算开上了船,不过天气不太好,灰蒙蒙的。

      +

      + +终于坐上了船 + + +

      +

      水上荡了1个小时,下船向佛香阁进发。

      +

      + +佛香阁 + + +

      +

      一路向上,终于登顶,园内风景一览无余。

      +

      下来后,又去逛了十七孔桥,逛了下湖心小岛。

      +

      回家又特意坐的双层公交,第一排观光区,哈哈。

      +

      一天下来1w多步,娃估计得更多,到家直接累摊。

      +

      + +这是鸳鸯吧 + + +

      +

      + +佛香阁 + + +

      +

      + +十七孔桥 + + +

      +]]>
      +
      + + 博客架构说明 + https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/ + Sun, 10 Apr 2022 20:41:57 +0800 + https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/ + <p>在拿到<code>liudon.com</code>域名前,手中已有两个域名:</p> +<ul> +<li>liudon.org</li> +<li>liudon.xyz</li> +</ul> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu10108568587195088134.webp 653w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu6151068063620748445.png 653w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="%e5%8d%9a%e5%ae%a2%e6%9e%b6%e6%9e%84.png" width="653" height="206" alt="两套域名说明" title="" loading="lazy" /> + </picture> + +</p> +<p><code>liudon.org</code>已经不再更新,仅作归档使用。 +<code>liudon.xyz</code>当时是静态博客流行,尝鲜使用。</p> + 在拿到liudon.com域名前,手中已有两个域名:

      +
        +
      • liudon.org
      • +
      • liudon.xyz
      • +
      +

      + +两套域名说明 + + +

      +

      liudon.org已经不再更新,仅作归档使用。 +liudon.xyz当时是静态博客流行,尝鲜使用。

      +

      拿到liudon.com域名后,怎么部署博客成了个问题。

      +

      因为github pages只能绑定一个自定义域名,当然可以通过创建另外一个项目,实现两套域名,但是同一个博客两个项目总感觉不太优雅。

      +

      经过一番资料查找,终于有了下面这套方案。

      +

      + +博客构建流程 + + +

      +

      通过github actionsnetlify 部署了两套自动化部署方案:

      +
        +
      • github actions部署到github pages,绑定自定义域名liudon.com
      • +
      • netlify部署到ipfs,通过cloudfare ipfs gateway解析到ipfs资源,绑定自定义域名liudon.xyz
      • +
      +

      这里要说明一下,ipfs目前访问延迟较大,这里仅作尝鲜使用。

      +

      hugoconfig.toml定义了网站域名,这里为了区分两套域名,在netlify部署时,对配置文件做了修改,保证两套域名访问各自页面,具体可参考github文件内容

      +]]>
      +
      + + 难得的清明假期 + https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/ + Wed, 06 Apr 2022 08:06:19 +0800 + https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/ + <p>前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。</p> +<p>趁着这次难得的假期,外出放松一下。</p> +<ol> +<li> +<p>爬百望山。</p> +<p>娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。</p> + 前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。

      +

      趁着这次难得的假期,外出放松一下。

      +
        +
      1. +

        爬百望山。

        +

        娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。

        +

        进园后,选择大路往上爬,中间走了一段山间台阶路。

        +

        一路走走停停,补充能量,估计有半个小时,我们就登顶了。

        +

        山上风有点大,视野不错,可以直接看到互联网的核心公司。

        +
      2. +
      3. +

        游北海公园

        +

        本来是想着去公园划船,结果到了之后发现排队的人太多了,于是放弃,改为游白塔。

        +

        我们是从北门进的园,然后一路向西去码头打算划船,绕着园子继续往南走。

        +

        一路走到了南门,心想这出去了还咋去白塔,难道要再走回去?

        +

        问了门口保安,说是可以出去往东走,从南门再进去。

        +

        进去后,要爬好几层楼梯才能上到白塔平台,一览园内风景。

        +

        + +北海白塔 + + +

        +
      4. +
      +

      希望疫情尽快散去,恢复到正常的生活。

      +]]>
      +
      + + 十一年的等待,终于拿到了liudon.com域名 + https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/ + Fri, 01 Apr 2022 01:24:05 +0800 + https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/ + <p>在<a href="https://liudon.com/about">关于</a>部分,有写域名的来历。</p> +<p>当时<code>liudon.com</code>已经被注册,所以只好注册了<code>liudon.org</code>。</p> +<p>2011年注册的<code>liudon.org</code>,最早用<code>wordpress</code>搭建了博客。</p> + 关于部分,有写域名的来历。

      +

      当时liudon.com已经被注册,所以只好注册了liudon.org

      +

      2011年注册的liudon.org,最早用wordpress搭建了博客。

      +

      这是当时的第一篇文章,Hello world

      +

      从2011年,到2019年,中间断断续续的在更新着,博客程序也从wordpress转到了typecho,再后来开始流行静态页博客,又转到了hugo下。

      +

      在这中间,一直想要拿到liudon.com域名。

      +

      看网站内容,应该是个德国乐队的网站,这里能看到网站最后的信息

      +

      3月10日的时候,偶然看到群里有在说域名,于是又搜索了一番,发现域名上了阿里云域名拍卖,购买的时候提示卖家已下家。

      +

      到国外域名商上查了一下,godaddy提供代购,name提示为高级域名,可以购买。

      +

      archive.org上的信息,2021年11月的时候服务不可用了,不知道发生了什么。

      +

      机不可失,时不再来,立马下单。

      +

      接下来就是漫长的等待了,系统显示需要5-10个工作日才能完成交易。

      +

      同时我发现他们服务条款写着,高级域名不支持退款,whois信息一直不变,担心打水漂。

      +

      期间有一天晚上11点联系客户,说是已经在加快处理。

      +

      过了10天后,交易还没完成,有点放弃了。

      +

      3月24日晚上,再次查whois,发现信息有变,登录name,发现订单已完成。

      +

      现在这个域名终于属于我了。

      +
      liudon.org 老站,备份,不再更新
      +liudon.com -> github pages
      +liudon.xyz -> netfily -> ipfs
      +
      ]]>
      +
      + + 被隔离的一周 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + Fri, 01 Apr 2022 01:20:39 +0800 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + <p>从没有想过疫情会离自己这么近,记录一下。</p> +<p>周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。</p> +<p>周二早上全员核酸阴性,继续到公司上班。</p> + 从没有想过疫情会离自己这么近,记录一下。

      +

      周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。

      +

      周二早上全员核酸阴性,继续到公司上班。

      +

      晚上8点10分刚和同事上地铁,部门群里有人说我们楼有人确诊,过了10分钟,说是大楼管控了,不让出楼了。

      +

      封在楼里的同学统一做核酸,结果出来后才能回家,好多人在公司过了夜。

      +

      周三到公司集体核酸,做完继续居家办公,今天开始公安局、社区开始联系确认信息,公司要求所有人员居家办公,社区反馈需要居家隔离。

      +

      头几天还说对门上封条了,没成想这次轮到自己了。

      +

      到03/31日,隔离解除,健康宝恢复正常。

      +

      第一次体验到健康宝显示弹窗,然后又显示居家隔离。

      +

      第一次体验到被贴封条上门磁。

      +

      第一次体验到鼻拭子,尤其解封前最后一次核酸,双鼻孔。

      +]]>
      +
      + + mysql中字符串和整型自动转换的问题 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + Mon, 14 Dec 2020 18:47:29 +0800 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + <p>表结构如下</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>desc info; +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> Field <span style="color:#f92672">|</span> Type <span style="color:#f92672">|</span> Null <span style="color:#f92672">|</span> Key <span style="color:#f92672">|</span> Default <span style="color:#f92672">|</span> Extra <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> id <span style="color:#f92672">|</span> <span style="color:#a6e22e">int</span>(<span style="color:#ae81ff">8</span>) unsigned <span style="color:#f92672">|</span> NO <span style="color:#f92672">|</span> PRI <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> auto_increment <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> name <span style="color:#f92672">|</span> varchar(<span style="color:#ae81ff">20</span>) <span style="color:#f92672">|</span> YES <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span> rows <span style="color:#f92672">in</span> set (<span style="color:#ae81ff">0.00</span> sec) +</span></span></code></pre></div><p>执行sql.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;xxx&#39;); +</span></span><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;yyy&#39;); +</span></span></code></pre></div><p>查询记录.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>select * from info; +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| id | name | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| 1 | xxx | +</span></span><span style="display:flex;"><span>| 2 | yyy | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>2 rows in set (0.00 sec) +</span></span></code></pre></div><p>执行下面sql.</p> + 表结构如下

      +
      desc info;
      ++-------+-----------------+------+-----+---------+----------------+
      +| Field | Type            | Null | Key | Default | Extra          |
      ++-------+-----------------+------+-----+---------+----------------+
      +| id    | int(8) unsigned | NO   | PRI | NULL    | auto_increment |
      +| name  | varchar(20)     | YES  |     | NULL    |                |
      ++-------+-----------------+------+-----+---------+----------------+
      +2 rows in set (0.00 sec)
      +

      执行sql.

      +
      insert into info values ('', 'xxx');
      +insert into info values ('', 'yyy');
      +

      查询记录.

      +
      select * from info;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      +|  2 | yyy  |
      ++----+------+
      +2 rows in set (0.00 sec)
      +

      执行下面sql.

      +
      select * from info where id = 1;
      +
      +select * from info where id = '1aaaa';
      +

      你先想想结果会是什么。

      +
      select * from info where id = 1;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set (0.00 sec)
      +
      +select * from info where id = '1aaaa';
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set, 1 warning (0.01 sec)
      +

      两个sql都查到了id = 1的记录,唯一的区别在于第二个sql有一个warning错误。

      +
      show warnings;
      ++---------+------+-------------------------------------------+
      +| Level   | Code | Message                                   |
      ++---------+------+-------------------------------------------+
      +| Warning | 1292 | Truncated incorrect DOUBLE value: '1aaaa' |
      ++---------+------+-------------------------------------------+
      +1 row in set (0.00 sec)
      +

      mysql在查询时,会根据字段类型进行转换,这里1aaaa被转为了1

      +]]>
      +
      + + 一次惊心动魄的Mysql更新操作 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + Tue, 19 May 2020 17:16:53 +0800 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + <h4 id="问题描述">问题描述</h4> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span># 表结构 +</span></span><span style="display:flex;"><span>MySQL &gt; desc user_packages; +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| Field | Type | Null | Key | Default | Extra | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | +</span></span><span style="display:flex;"><span>| start_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| end_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>| up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>MySQL &gt; select * from user_packages limit 5; +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| up_id | start_date | end_date | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| 185 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 186 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 187 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 188 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 189 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span></code></pre></div><h4 id="操作过程">操作过程</h4> +<p>需要更新某条记录的<code>end_date</code>字段,执行sql如下:</p> + 问题描述 +
      # 表结构
      +MySQL > desc user_packages;
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| Field          | Type                | Null | Key | Default             | Extra          |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| up_id          | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
      +| start_date     | date                | NO   |     | NULL                |                |
      +| end_date       | date                | NO   |     | NULL                |                |
      +| up_created     | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
      +| up_updated     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +5 rows in set (0.00 sec)
      +
      +MySQL > select * from user_packages limit 5;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 2018-06-30 |
      +|   186 | 2018-04-01 | 2018-06-30 |
      +|   187 | 2018-04-01 | 2018-06-30 |
      +|   188 | 2018-04-01 | 2018-06-30 |
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +5 rows in set (0.00 sec)
      +

      操作过程

      +

      需要更新某条记录的end_date字段,执行sql如下:

      +
      MySQL > update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +Query OK, 1 row affected, 1 warning (0.00 sec)
      +Rows matched: 1  Changed: 1  Warnings: 1
      +

      执行完,发现sql写错了!!!!

      +

      正确的sql应该是:

      +
      update user_packages set end_date = '2020-06-06' where up_id = 189 limit 1;
      +

      误把where写成了and,还好指定了limit = 1,只操作了一条记录。

      +

      回滚

      +

      回滚的前提,要先找到更新的那条记录。

      +

      up_id为表的主键,更新前表里已经有这条记录了,主键不能重复,感觉语句应该没有执行成功。

      +
      MySQL > select * from user_packages where up_id = 189;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      执行查询语句,表里确实也只有这一条up_id=189的记录。

      +

      感觉这个update语句应该没执行成功,但是没执行成功应该报错的呀。

      +

      这个时候把希望放到了语句结果里的Warnings: 1,是不是没执行成功呢。

      +

      因为紧接着又执行了其他语句,所以也无法通过show warnings查看具体的错误信息了。

      +

      那么这条语句到底执行成功了吗?如果执行成功,那么修改的是哪条记录呢?

      +

      这里通过一番查找,终于定位到了记录。

      +

      AND分隔符,在mysql语句里优先级最高。

      +
      update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +
      +等效为
      +
      +update user_packages set end_date = ('2020-06-06' and up_id = 189) limit 1;
      +
      +即
      +
      +update user_packages set end_date = 0 limit 1;
      +

      因为end_date字段为date类型,所以写入表后的记录为0000-00-00

      +
      MySQL > select * from user_packages where end_date = '0000-00-00';
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 0000-00-00 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      好在这次只更新了一条记录,否则后果无法想象。

      +

      切记不要在现网直接操作DB。

      +

      相关资料:

      +

      一个我认为是bug的UPDATE语句

      +]]>
      +
      + + 如何在北京公积金网站上修改婚姻状况 + https://liudon.com/posts/how-to-modify-marital-status/ + Fri, 17 Jan 2020 17:14:32 +0800 + https://liudon.com/posts/how-to-modify-marital-status/ + <blockquote> +<p>关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告</p> +<p>时间:2020年01月08日</p> +<p>来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html</p> + +

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告

      +

      时间:2020年01月08日

      +

      来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html

      + +

      1月8日,北京公积金中心发文,从1月10日开始可以网上办理公积金提取了。

      +

      这里单独讲一下外地领证的情况下,如何修改婚姻状况。

      +
        +
      1. +

        进入提取页面,默认显示为未说明的婚姻状况。

        +

        + +QQ截图20200117170836.jpg + + +

        +
      2. +
      3. +

        点击婚姻状况,选择已婚。

        +

        + +QQ截图20200117170917.jpg + + +

        +

        可以看到婚姻状态相关的输入框都为灰色,不可修改。

        +
      4. +
      5. +

        点击婚姻信息修改按钮,会弹出一个民政校验的弹窗,因为我是外地领证,这里查不到信息。

        +

        + +QQ截图20200117171149.jpg + + +

        +

        注意图片右下角还是只有一个婚姻信息修改按钮。

        +
      6. +
      7. +

        点击弹窗里的确认按钮。

        +

        + +QQ截图20200117171236.jpg + + +

        +

        这下婚姻状况相关的输入框都可以填写了。

        +

        另外图片右下角里多了一个上传结婚证按钮。

        +
      8. +
      9. +

        填写完信息后,点击上传结婚证按钮。

        +

        + +QQ截图20200117171252.jpg + + +

        +
      10. +
      11. +

        按说明上传两张结婚证照片,点击确认即可。

        +
      12. +
      13. +

        其余的按公积金官网文档操作,最后提交即可。

        +
      14. +
      +

      关于提取时间,我是前一天中午申请,第二天下午就到账了,效率还是很棒的。

      +]]>
      +
      + + PHP7.2编译安装后没有php.ini文件的问题 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + Tue, 26 Nov 2019 19:56:18 +0800 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + <p>下载PHP7.2源码,编译安装。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v +</span></span><span style="display:flex;"><span>PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) +</span></span><span style="display:flex;"><span>Copyright (c) 1997-2018 The PHP Group +</span></span><span style="display:flex;"><span>Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies +</span></span><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# +</span></span></code></pre></div><p>安装Swoole。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>phpize &amp;&amp; \ +</span></span><span style="display:flex;"><span>./configure &amp;&amp; \ +</span></span><span style="display:flex;"><span>make &amp;&amp; make install +</span></span></code></pre></div><p>安装完,准备修改<code>php.ini</code>文件,结果没找到。</p> + 下载PHP7.2源码,编译安装。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v
      +PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )
      +Copyright (c) 1997-2018 The PHP Group
      +Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]# 
      +

      安装Swoole。

      +
      phpize && \
      +./configure && \
      +make && make install
      +

      安装完,准备修改php.ini文件,结果没找到。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll /usr/local/services/php/etc/
      +total 88
      +-rw-r--r-- 1 root root  1364 Nov 26 19:34 pear.conf
      +-rw-r--r-- 1 root root  4508 Nov 26 19:34 php-fpm.conf.default
      +drwxr-xr-x 2 root root  4096 Nov 26 19:34 php-fpm.d
      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => (none)
      +

      这是什么鬼,居然没有php.ini文件。

      +

      原来PHP源码里提供了两个php.ini文件,你需要按需拷贝到你的PHP的目录下。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll ../php-7.2.25 | grep ini
      +-rw-rw-r--  1 root root   71232 Nov 20 23:11 php.ini-development
      +-rw-rw-r--  1 root root   71413 Nov 20 23:11 php.ini-production
      +

      拷贝后。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => /usr/local/services/php/etc/php.ini
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]#
      +
      ]]>
      +
      + + 检测网站支持的SSL/TLS协议版本 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + <blockquote> +<p>Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。</p> +</blockquote> +<p>为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。</p> + +

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。

      + +

      为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。

      +
        +
      1. +

        检测是否支持TLSv1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1
        +
      2. +
      3. +

        检测是否支持TLSv1.1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_1
        +
      4. +
      5. +

        检测是否支持TLSv1.2

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_2
        +
      6. +
      +

      参考资料:How to check what SSL/TLS versions are available for a website?

      +]]>
      +
      + + 记一次难忘的手术经历 + https://liudon.com/posts/an-unforgettable-surgical-experience/ + Mon, 28 Oct 2019 19:03:31 +0800 + https://liudon.com/posts/an-unforgettable-surgical-experience/ + 有痔青年的手术经历 + 俗话说的好,十人九痔。这九个人里就有我一个。 😂

      +

      去年因为痔疮去过一趟医院,医生当时建议手术。

      +

      后来用了点药,没啥症状了,就不放在心上了。

      +

      结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。

      +

      第二天赶紧上医院检查,先上开塞露通便,通完舒服多了。

      +

      医生检查完,让住院手术,这次狠了狠心,手术做掉,一了百了。

      +
        +
      1. 10/15住院
      2. +
      3. 10/16手术
      4. +
      5. 10/22出院
      6. +
      +

      上一次手术是在10年前了,全麻,什么都不知道。

      +

      这次是局麻,上手术台后,医生往腰上打了一针,很疼。

      +

      过了10分钟左右,肛门已经使不上劲了,开始手术。

      +

      过程一直感觉有往里打气,东西插进去。

      +

      医生说有用镇静,后来越来越困,感觉要晕过去,不过还是撑着没睡。

      +

      出来后,整个下午下半身都是麻的,没啥精神,事后就记得媳妇坐在我对面。

      +

      术后6个小时内要小便,不然就得插尿管。

      +

      第二天开始喝流食,然后就是每天医生给换药。

      +

      第三天开始大便,伤口那个痛啊。 😭😭😭

      +

      后来学会在热水里泡着拉,感觉舒服了点。

      +

      每天都要担心大便,担心不拉,或者担心拉的太多。

      +

      出院后第二天,早上大便一盆血,以为是大出血,把媳妇叫回来直奔医院。

      +

      到医院病房,主治医生不在,找了个其他医生给检查,说是有点出血。

      +

      检查的时候塞了个肛门镜,这玩意实在是痛苦。

      +

      然后说病房没工具,上门诊处理吧。然后我就拖着身子,走到门诊。

      +

      主治医生说没啥大事,其实可以不处理,不过你也是来医院了,还是处理一下吧。

      +

      处理的时候,又塞了个肛门镜,然后往屁眼上打了三针封闭,想死的心都有了。

      +

      回家后,继续卧床休息,每天换药。

      +

      朋友们,请一定要好好对你的🌻。

      +]]>
      +
      + + 十一假期经历 + https://liudon.com/posts/chinese-national-day/ + Tue, 08 Oct 2019 13:13:36 +0800 + https://liudon.com/posts/chinese-national-day/ + <p>今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。</p> +<p>只好请了2天假,提前回家了,给自己也放个假休息一下。</p> +<hr> +<p>回家的几个经历:</p> +<ol> +<li> +<p>家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。</p> + 今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。

      +

      只好请了2天假,提前回家了,给自己也放个假休息一下。

      +
      +

      回家的几个经历:

      +
        +
      1. +

        家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。

        +

        + +64F68D7789A8D877A41E6D694ABE5444.png + + +

        +
      2. +
      3. +

        村里今年也要通天然气了,各家各户都要拆煤炉,装壁挂炉,希望天然气供应不出问题。

        +
      4. +
      5. +

        全家一起去了趟家门口的园博园,希望以后可以走的更远一些。

        +

        + +F69E19D28B34C163878F2A6E1A43E039.png + + +

        +
      6. +
      7. +

        娃确实是长大了,有了自己的想法,有自己的脾气了。

        +
        +

        背景:晚上开着灯,要睡觉了。

        +

        我:睡觉吧。

        +

        娃:爸爸,你说开着灯能睡觉吗?

        +

        我:不能吧…(不知道她为啥问这个问题)

        +

        娃:那你开着灯让我睡觉,我怎么睡呀!

        +

        我:原来你在这里等着我呢啊…

        +
        +
      8. +
      +
      +

      接下来,就努力工作吧。

      +]]>
      +
      + + Swoft 框架运行分析(五) —— ConsoleProcessor模块分析 + https://liudon.com/posts/swoft-console-processor-analysis/ + Thu, 26 Sep 2019 13:14:23 +0800 + https://liudon.com/posts/swoft-console-processor-analysis/ + <blockquote> +<p>这里以Swoft启动http server为例。</p> +<p>php bin/swoft http:start</p> +<p>执行上述命令,启动http server。</p> +</blockquote> +<p>在前面第一篇文章的时候,提到了如何启动http服务。</p> +<p>今天我们就来看一下http服务是如何启动的,具体实现就在<code>ConsoleProcess</code>这个模块。</p> + +

      这里以Swoft启动http server为例。

      +

      php bin/swoft http:start

      +

      执行上述命令,启动http server。

      + +

      在前面第一篇文章的时候,提到了如何启动http服务。

      +

      今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。

      +
      /**
      +    * Handle console
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeConsole()) {
      +        return false;
      +    }
      +
      +    /** @var Router $router */
      +    $router = bean('cliRouter');
      +
      +    // Register console routes
      +    CommandRegister::register($router);
      +
      +    CLog::info(
      +        'Console command route registered (group %d, command %d)',
      +        $router->groupCount(),
      +        $router->count()
      +    );
      +
      +    // Run console application
      +    bean('cliApp')->run();
      +
      +    return $this->application->afterConsole();
      +}
      +

      这里调用了bean方法获取Bean实例,定义见swoft-component-2.0.5\src\bean\src\Helper\Functions.php

      +
      if (!function_exists('bean')) {
      +    /**
      +     * Get bean by name
      +     *
      +     * @param string $name Bean name Or alias Or class name
      +     *
      +     * @return object|string|mixed
      +     */
      +    function bean(string $name)
      +    {
      +        if (BeanFactory::isSingleton('config')) {
      +            return BeanFactory::getBean($name);
      +        }
      +
      +        return sprintf('${%s}', $name);
      +    }
      +}
      +

      这里调用了BeanFactorygetBean方法。

      +
      /**
      +    * Get object by name
      +    *
      +    * @param string $name Bean name Or alias Or class name
      +    *
      +    * @return object|mixed
      +    */
      +public static function getBean(string $name)
      +{
      +    return Container::getInstance()->get($name);
      +}
      +

      最终调用的是Swoft\Bean\Container下的get方法。

      +
      /**
      +    * Finds an entry of the container by its identifier and returns it.
      +    *
      +    * @param string $id Bean name Or alias Or class name
      +    *
      +    * When class name will return all of instance for class name
      +    *
      +    * @return object
      +    * @throws InvalidArgumentException
      +    */
      +public function get($id)
      +{
      +    // It is singleton
      +    if (isset($this->singletonPool[$id])) {
      +        return $this->singletonPool[$id];
      +    }
      +
      +    // Prototype by clone
      +    if (isset($this->prototypePool[$id])) {
      +        return clone $this->prototypePool[$id];
      +    }
      +
      +    // Alias name
      +    $aliasId = $this->aliases[$id] ?? '';
      +    if ($aliasId) {
      +        return $this->get($aliasId);
      +    }
      +
      +    // Class name
      +    $classNames = $this->classNames[$id] ?? [];
      +    if ($classNames) {
      +        $id = end($classNames);
      +        return $this->get($id);
      +    }
      +
      +    // Interface
      +    if (interface_exists($id)) {
      +        $id = InterfaceRegister::getInterfaceInjectBean($id);
      +        return $this->get($id);
      +    }
      +
      +    // Not defined
      +    if (!isset($this->objectDefinitions[$id])) {
      +        throw new InvalidArgumentException(sprintf('The bean of %s is not defined', $id));
      +    }
      +
      +    /* @var ObjectDefinition $objectDefinition */
      +    $objectDefinition = $this->objectDefinitions[$id];
      +
      +    // Prototype
      +    return $this->safeNewBean($objectDefinition->getName());
      +}
      +

      获取对应的ObjectDefinition实例,然后调用safeNewBean方法。

      +
      /**
      +    * Secure creation of beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object|mixed
      +    */
      +private function safeNewBean(string $beanName, string $id = '')
      +{
      +    try {
      +        return $this->newBean($beanName, $id);
      +    } catch (Throwable $e) {
      +        throw new InvalidArgumentException($e->getMessage(), 500, $e);
      +    }
      +}
      +

      这里又调用了newBean方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的Bean类。

      +

      cliRouter对应的类是说明呢?这个定义在swoft-component-2.0.5\src\console\src\AutoLoader.php里。

      +
      /**
      +    * {@inheritDoc}
      +    */
      +public function beans(): array
      +{
      +    return [
      +        'cliApp'    => [
      +            'class'   => Application::class,
      +            'version' => '2.0.0'
      +        ],
      +        'cliRouter' => [
      +            'class' => Router::class,
      +        ],
      +        'cliDispatcher' => [
      +            'class' => ConsoleDispatcher::class,
      +        ],
      +    ];
      +}
      +

      所以$router = bean('cliRouter'),返回的是一个Swoft\Console\Router\Router类。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CommandRegister::register($router);
      +

      调用了CommandRegister类的register方法。

      +
      
      +/**
      +    * @param Router $router
      +    * @throws ReflectionException
      +    */
      +public static function register(Router $router): void
      +{
      +    $maxLen  = 12;
      +    $groups  = [];
      +    $docOpts = [
      +        'allow' => ['example']
      +    ];
      +    $defInfo = [
      +        'example'     => '',
      +        'description' => 'No description message',
      +    ];
      +
      +    foreach (self::$commands as $class => $mapping) {
      +        $names = [];
      +        $group = $mapping['group'];
      +        // Set ID aliases
      +        $router->setIdAliases($mapping['idAliases']);
      +        // Set group name aliases
      +        $router->setGroupAliases($group, $mapping['aliases']);
      +
      +        $refInfo = Swoft::getReflection($class);
      +        $mhdInfo = $refInfo['methods'] ?? [];
      +        $grpOpts = $mapping['options'] ?? [];
      +
      +        foreach ($mapping['commands'] as $method => $route) {
      +            // $method = $route['method'];
      +            $cmdDesc = $route['desc'];
      +            $command = $route['command'];
      +
      +            $idLen = strlen($group . $command);
      +            if ($idLen > $maxLen) {
      +                $maxLen = $idLen;
      +            }
      +
      +            $cmdExam = '';
      +            if (!empty($mhdInfo[$method]['comments'])) {
      +                $tagInfo = DocBlock::getTags($mhdInfo[$method]['comments'], $docOpts, $defInfo);
      +                $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo['description']);
      +                $cmdExam = $tagInfo['example'];
      +            }
      +
      +            $route['group']   = $group;
      +            $route['desc']    = ucfirst($cmdDesc);
      +            $route['example'] = $cmdExam;
      +            $route['options'] = self::mergeOptions($grpOpts, $route['options']);
      +            // Append group option
      +            $route['enabled']   = $mapping['enabled'];
      +            $route['coroutine'] = $mapping['coroutine'];
      +
      +            $router->map($group, $command, [$class, $method], $route);
      +            $names[] = $command;
      +        }
      +
      +        $groupExam = '';
      +        $groupDesc = $mapping['desc'];
      +        if (!empty($refInfo['comments'])) {
      +            $tagInfo   = DocBlock::getTags($refInfo['comments'], $docOpts, $defInfo);
      +            $groupDesc = $groupDesc ?: Str::firstLine($tagInfo['description']);
      +            $groupExam = $tagInfo['example'];
      +        }
      +
      +        $groups[$group] = [
      +            'names'   => $names,
      +            'desc'    => ucfirst($groupDesc),
      +            'class'   => $class,
      +            'alias'   => $mapping['alias'],
      +            'aliases' => $mapping['aliases'],
      +            'example' => $groupExam,
      +        ];
      +    }
      +
      +    $router->setGroups($groups);
      +    // +1 because router->delimiter
      +    $router->setKeyWidth($maxLen + 1);
      +    // clear data
      +    self::$commands = [];
      +}
      +

      这里遍历了类属性$commands注册路由。

      +

      那么$commands这个属性是哪里来的呢?

      +

      既然开头我们说的是http服务是怎么启动的,这里我们就以http-server来举例,找到swoft-component-2.0.5\src\http-server\src\Command\HttpServerCommand.php文件。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server\Command;
      +
      +use ReflectionException;
      +use Swoft;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Console\Annotation\Mapping\Command;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\Annotation\Mapping\CommandOption;
      +use Swoft\Console\Helper\Show;
      +use Swoft\Http\Server\HttpServer;
      +use Swoft\Server\Command\BaseServerCommand;
      +use Swoft\Server\Exception\ServerException;
      +use function bean;
      +use function input;
      +use function output;
      +
      +/**
      + * Provide some commands to manage the swoft HTTP server
      + *
      + * @since 2.0
      + *
      + * @Command("http", alias="httpsrv", coroutine=false)
      + * @example
      + *  {fullCmd}:start     Start the http server
      + *  {fullCmd}:stop      Stop the http server
      + */
      +class HttpServerCommand extends BaseServerCommand
      +{
      +    /**
      +     * Start the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +     * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @throws ServerException
      +     * @example
      +     *   {fullCommand}
      +     *   {fullCommand} -d
      +     *
      +     */
      +    public function start(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $masterPid = $server->getPid();
      +            output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +            return;
      +        }
      +
      +        // Startup settings
      +        $this->configStartOption($server);
      +
      +        $settings = $server->getSetting();
      +        // Setting
      +        $workerNum = $settings['worker_num'];
      +
      +        // Server startup parameters
      +        $mainHost = $server->getHost();
      +        $mainPort = $server->getPort();
      +        $modeName = $server->getModeName();
      +        $typeName = $server->getTypeName();
      +
      +        // Http
      +        $panel = [
      +            'HTTP' => [
      +                'listen' => $mainHost . ':' . $mainPort,
      +                'type'   => $typeName,
      +                'mode'   => $modeName,
      +                'worker' => $workerNum,
      +            ],
      +        ];
      +
      +        // Port Listeners
      +        $panel = $this->appendPortsToPanel($server, $panel);
      +
      +        Show::panel($panel);
      +
      +        output()->writeln('<success>HTTP server start success !</success>');
      +
      +        // Start the server
      +        $server->start();
      +    }
      +
      +    /**
      +     * Reload worker processes
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-t]")
      +     * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function reload(): void
      +    {
      +        $server = $this->createServer();
      +        $script = input()->getScript();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot reload</error>');
      +            return;
      +        }
      +
      +        output()->writef('<info>Server %s is reloading</info>', $script);
      +
      +        if ($reloadTask = input()->hasOpt('t')) {
      +            Show::notice('Will only reload task worker');
      +        }
      +
      +        if (!$server->reload($reloadTask)) {
      +            Show::error('The swoole server worker process reload fail!');
      +            return;
      +        }
      +
      +        output()->writef('<success>HTTP server %s reload success</success>', $script);
      +    }
      +
      +    /**
      +     * Stop the currently running server
      +     *
      +     * @CommandMapping()
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function stop(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot stop.</error>');
      +            return;
      +        }
      +
      +        // Do stopping.
      +        $server->stop();
      +    }
      +
      +    /**
      +     * Restart the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
      +     * @CommandOption("daemon", short="d", desc="Run server on the background")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @example
      +     *  {fullCommand}
      +     *  {fullCommand} -d
      +     */
      +    public function restart(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $success = $server->stop();
      +
      +            if (!$success) {
      +                output()->error('Stop the old server failed!');
      +                return;
      +            }
      +        }
      +
      +        output()->writef('<success>Server HTTP restart success !</success>');
      +        $server->startWithDaemonize();
      +    }
      +
      +    /**
      +     * @return HttpServer
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    private function createServer(): HttpServer
      +    {
      +        $script  = input()->getScript();
      +        $command = $this->getFullCommand();
      +
      +        /** @var HttpServer $server */
      +        $server = bean('httpServer');
      +        $server->setScriptFile(Swoft::app()->getPath($script));
      +        $server->setFullCommand($command);
      +
      +        return $server;
      +    }
      +}
      +

      通过Swoft文档,我们可以看到这里分别使用了类注解和方法注解。

      +
      @Command("http", alias="httpsrv", coroutine=false)
      +@CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +@CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +...
      +

      通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。

      +

      这里以Swoft\Console\Annotation\Mapping\CommandMapping这个注解为例,对应的注解解析类为Swoft\Console\Annotation\Parser\CommandMappingParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Console\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\CommandRegister;
      +
      +/**
      + * Class CommandMappingParser
      + *
      + * @since 2.0
      + * @AnnotationParser(CommandMapping::class)
      + */
      +class CommandMappingParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int            $type Class or Method or Property
      +     * @param CommandMapping $annotation Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_METHOD) {
      +            throw new AnnotationException('`@CommandMapping` must be defined on class method!');
      +        }
      +
      +        $method = $this->methodName;
      +
      +        // add route info for controller action
      +        CommandRegister::addRoute($this->className, $method, [
      +            'command' => $annotation->getName() ?: $method,
      +            'method'  => $method,
      +            'alias'   => $annotation->getAlias(),
      +            'aliases' => $annotation->getAliases(),
      +            'desc'    => $annotation->getDesc(),
      +            'usage'   => $annotation->getUsage(),
      +            // 'example' => $annotation->getExample(),
      +        ]);
      +
      +        return [];
      +    }
      +}
      +

      看到这里,你应该可以猜到CommandRegister类的$commands是怎么来的了吧。

      +

      我们看下CommandRegister类的addRoute方法,验证下想法。

      +
      /**
      +    * @param string $class
      +    * @param string $method
      +    * @param array  $route
      +    *
      +    * @throws AnnotationException
      +    */
      +public static function addRoute(string $class, string $method, array $route): void
      +{
      +    self::checkClass($class);
      +
      +    // init some keys
      +    $route['options']   = [];
      +    $route['arguments'] = [];
      +    // save
      +    self::$commands[$class]['commands'][$method] = $route;
      +}
      +

      bingo,跟我们猜想的一模一样,这下我们也知道CommandMapping这个注解是用来注册终端的路由信息。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CLog::info(
      +    'Console command route registered (group %d, command %d)',
      +    $router->groupCount(),
      +    $router->count()
      +);
      +

      打印日志。

      +
      // Run console application
      +bean('cliApp')->run();
      +

      感觉到了重头戏。

      +

      根据前面的代码,我们知道cliApp这个Bean实例对应的类是Swoft\Console\Application

      +
      /**
      +    * @return void
      +    * @throws ContainerException
      +    */
      +public function run(): void
      +{
      +    try {
      +        Swoft::trigger(ConsoleEvent::RUN_BEFORE, $this);
      +
      +        // Prepare
      +        $this->prepare();
      +
      +        // Get input command
      +        $inputCommand = $this->input->getCommand();
      +
      +        if (!$inputCommand) {
      +            $this->filterSpecialOption();
      +        } else {
      +            $this->doRun($inputCommand);
      +        }
      +
      +        Swoft::trigger(ConsoleEvent::RUN_AFTER, $this, $inputCommand);
      +    } catch (Throwable $e) {
      +        /** @var ConsoleErrorDispatcher $errDispatcher */
      +        $errDispatcher = BeanFactory::getSingleton(ConsoleErrorDispatcher::class);
      +
      +        // Handle request error
      +        $errDispatcher->run($e);
      +    }
      +}
      +

      通过Swoft::trigger,注册了ConsoleEvent::RUN_BEFOREConsoleEvent::RUN_AFTER两个事件。

      +
      protected function prepare(): void
      +{
      +    $this->input  = \input();
      +    $this->output = \output();
      +
      +    // load builtin comments vars
      +    $this->setCommentsVars($this->commentsVars());
      +}
      +

      prepare比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。

      +
      $inputCommand = $this->input->getCommand();
      +if (!$inputCommand) {
      +    $this->filterSpecialOption();
      +} else {
      +    $this->doRun($inputCommand);
      +}
      +

      获取终端命令行下的输入,如果有输入执行doRun方法。

      +
      /**
      +    * @param string $inputCmd
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws Throwable
      +    */
      +protected function doRun(string $inputCmd): void
      +{
      +    $output = $this->output;
      +    /* @var Router $router */
      +    $router = Swoft::getBean('cliRouter');
      +    $result = $router->match($inputCmd);
      +
      +    // Command not found
      +    if ($result[0] === Router::NOT_FOUND) {
      +        $names = $router->getAllNames();
      +        $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +        // find similar command names by similar_text()
      +        if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +            $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +        } else {
      +            $this->showApplicationHelp(false);
      +        }
      +        return;
      +    }
      +
      +    $info = $result[1];
      +
      +    // Only input a group name, display help for the group
      +    if ($result[0] === Router::ONLY_GROUP) {
      +        $this->showGroupHelp($info['group']);
      +        return;
      +    }
      +
      +    // Display help for a command
      +    if ($this->input->getSameOpt(['h', 'help'])) {
      +        $this->showCommandHelp($info);
      +        return;
      +    }
      +
      +    // Parse default options and arguments
      +    $this->bindCommandFlags($info);
      +    $this->input->setCommandId($info['cmdId']);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +
      +    // Call command handler
      +    /** @var ConsoleDispatcher $dispatcher */
      +    $dispatcher = Swoft::getSingleton('cliDispatcher');
      +    $dispatcher->dispatch($info);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, $this, $info);
      +}
      +
      $router = Swoft::getBean('cliRouter');
      +$result = $router->match($inputCmd);
      +

      获取cliRouter实例,根据输入匹配路由操作类。

      +
      /**
      +    * Match route by input command
      +    *
      +    * @param array $params [$route]
      +    *
      +    * @return array
      +    *
      +    * [
      +    *  status, info(array)
      +    * ]
      +    */
      +public function match(...$params): array
      +{
      +    $delimiter = $this->delimiter;
      +    $inputCmd  = trim($params[0], "$delimiter ");
      +    $noSepChar = strpos($inputCmd, $delimiter) === false;
      +
      +    // If use command ID alias
      +    if ($noSepChar && isset($this->idAliases[$inputCmd])) {
      +        $inputCmd = $this->idAliases[$inputCmd];
      +        // Must re-check
      +        $noSepChar = strpos($inputCmd, $delimiter) === false;
      +    }
      +
      +    if ($noSepChar && in_array($inputCmd, $this->defaultCommands, true)) {
      +        $group   = $this->defaultGroup;
      +        $command = $this->resolveCommandAlias($inputCmd);
      +
      +        // Only a group name
      +    } elseif ($noSepChar) {
      +        $group = $this->resolveGroupAlias($inputCmd);
      +
      +        if (isset($this->groups[$group])) {
      +            return [self::ONLY_GROUP, ['group' => $group]];
      +        }
      +
      +        return [self::NOT_FOUND];
      +    } else {
      +        $nameList = explode($delimiter, $inputCmd, 2);
      +
      +        if (count($nameList) === 2) {
      +            [$group, $command] = $nameList;
      +            // resolve command alias
      +            $command = $this->resolveCommandAlias($command);
      +        } else {
      +            $command = '';
      +            // $command = $this->defaultCommand;
      +            $group = $nameList[0];
      +        }
      +    }
      +
      +    $group = $this->resolveGroupAlias($group);
      +    // build command ID
      +    $commandID = $this->buildCommandID($group, $command);
      +
      +    if (isset($this->routes[$commandID])) {
      +        $info = $this->routes[$commandID];
      +        // append some info
      +        $info['cmdId'] = $commandID;
      +
      +        return [self::FOUND, $info];
      +    }
      +
      +    if ($group && isset($this->groups[$group])) {
      +        return [self::ONLY_GROUP, ['group' => $group]];
      +    }
      +
      +    return [self::NOT_FOUND];
      +}
      +

      这里会返回匹配后的路由信息。

      +

      回到doRun方法。

      +
      // Command not found
      +if ($result[0] === Router::NOT_FOUND) {
      +    $names = $router->getAllNames();
      +    $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +    // find similar command names by similar_text()
      +    if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +        $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +    } else {
      +        $this->showApplicationHelp(false);
      +    }
      +    return;
      +}
      +
      +$info = $result[1];
      +
      +// Only input a group name, display help for the group
      +if ($result[0] === Router::ONLY_GROUP) {
      +    $this->showGroupHelp($info['group']);
      +    return;
      +}
      +
      +// Display help for a command
      +if ($this->input->getSameOpt(['h', 'help'])) {
      +    $this->showCommandHelp($info);
      +    return;
      +}
      +

      根据返回的路由信息进行不同的处理。

      +
      // Parse default options and arguments
      +$this->bindCommandFlags($info);
      +$this->input->setCommandId($info['cmdId']);
      +
      +Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +

      绑定默认参数,注册ConsoleEvent::DISPATCH_BEFORE事件。

      +
      // Call command handler
      +/** @var ConsoleDispatcher $dispatcher */
      +$dispatcher = Swoft::getSingleton('cliDispatcher');
      +$dispatcher->dispatch($info);
      +

      获取cliDispatcherBean实例,对应Swoft\Console\ConsoleDispatcher类,调用dispatch方法。

      +
      /**
      +    * @param array $params
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws Throwable
      +    */
      +public function dispatch(...$params): void
      +{
      +    $route = $params[0];
      +    // Handler info
      +    [$className, $method] = $route['handler'];
      +
      +    // Bind method params
      +    $params = $this->getBindParams($className, $method);
      +    $object = Swoft::getSingleton($className);
      +
      +    // Blocking running
      +    if (!$route['coroutine']) {
      +        $this->before(get_parent_class($object), $method);
      +        PhpHelper::call([$object, $method], ...$params);
      +        $this->after($method);
      +        return;
      +    }
      +
      +    // Hook php io function
      +    Runtime::enableCoroutine();
      +
      +    // If in unit test env, has been in coroutine.
      +    if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
      +        $this->executeByCo($object, $method, $params);
      +        return;
      +    }
      +
      +    // Coroutine running
      +    srun(function () use ($object, $method, $params) {
      +        $this->executeByCo($object, $method, $params);
      +    });
      +}
      +

      获取路由对应的类和方法,通过Swoft::getSingleton($className);实例化对象。

      +

      如果未开启协程,则用PhpHelper::call([$object, $method], ...$params);调用对应的方法。

      +

      开启协程的话,使用$this->executeByCo($object, $method, $params);调用对应的方法。

      +

      我们前面启动命令是php bin/swoft http:start,这里对应的类就是Swoft\Http\Server\Command\HttpServerCommand,方法就是start

      +
      /**
      +    * Start the http server
      +    *
      +    * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +    * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +    *
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws ServerException
      +    * @example
      +    *   {fullCommand}
      +    *   {fullCommand} -d
      +    *
      +    */
      +public function start(): void
      +{
      +    $server = $this->createServer();
      +
      +    // Check if it has started
      +    if ($server->isRunning()) {
      +        $masterPid = $server->getPid();
      +        output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +        return;
      +    }
      +
      +    // Startup settings
      +    $this->configStartOption($server);
      +
      +    $settings = $server->getSetting();
      +    // Setting
      +    $workerNum = $settings['worker_num'];
      +
      +    // Server startup parameters
      +    $mainHost = $server->getHost();
      +    $mainPort = $server->getPort();
      +    $modeName = $server->getModeName();
      +    $typeName = $server->getTypeName();
      +
      +    // Http
      +    $panel = [
      +        'HTTP' => [
      +            'listen' => $mainHost . ':' . $mainPort,
      +            'type'   => $typeName,
      +            'mode'   => $modeName,
      +            'worker' => $workerNum,
      +        ],
      +    ];
      +
      +    // Port Listeners
      +    $panel = $this->appendPortsToPanel($server, $panel);
      +
      +    Show::panel($panel);
      +
      +    output()->writeln('<success>HTTP server start success !</success>');
      +
      +    // Start the server
      +    $server->start();
      +}
      +

      这里先调用了createServer方法。

      +
      /**
      +    * @return HttpServer
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +private function createServer(): HttpServer
      +{
      +    $script  = input()->getScript();
      +    $command = $this->getFullCommand();
      +
      +    /** @var HttpServer $server */
      +    $server = bean('httpServer');
      +    $server->setScriptFile(Swoft::app()->getPath($script));
      +    $server->setFullCommand($command);
      +
      +    return $server;
      +}
      +

      获取httpServerBean实例。

      +

      框架定义在swoft-component-2.0.5\src\http-server\src\AutoLoader.php,这里声明了onRequest回调事件。

      +
      'httpServer'      => [
      +    'on' => [
      +        SwooleEvent::REQUEST => bean(RequestListener::class)
      +    ]
      +],
      +

      业务定义在swoft-2.0.5\app\bean.php

      +
      'httpServer'        => [
      +    'class'    => HttpServer::class,
      +    'port'     => 18306,
      +    'listener' => [
      +        'rpc' => bean('rpcServer')
      +    ],
      +    'process'  => [
      +//            'monitor' => bean(MonitorProcess::class)
      +//            'crontab' => bean(CrontabProcess::class)
      +    ],
      +    'on'       => [
      +//            SwooleEvent::TASK   => bean(SyncTaskListener::class),  // Enable sync task
      +        SwooleEvent::TASK   => bean(TaskListener::class),  // Enable task must task and finish event
      +        SwooleEvent::FINISH => bean(FinishListener::class)
      +    ],
      +    /* @see HttpServer::$setting */
      +    'setting'  => [
      +        'task_worker_num'       => 12,
      +        'task_enable_coroutine' => true
      +    ]
      +],
      +

      createServer返回的是一个Swoft\Http\Server\HttpServer实例。

      +

      回到HttpServerCommand类的start方法。

      +
      // Start the server
      +$server->start();
      +

      调用Swoft\Http\Server\HttpServer类的start方法。

      +
      /**
      +    * Start server
      +    *
      +    * @throws ServerException
      +    * @throws ContainerException
      +    */
      +public function start(): void
      +{
      +    $this->swooleServer = new \Swoole\Http\Server($this->host, $this->port, $this->mode, $this->type);
      +    $this->startSwoole();
      +}
      +

      声明Swoole\Http\Server对象,调用startSwoole方法。

      +

      Swoft\Http\Server\HttpServer类继承自Swoft\Server\Server类,startSwoole方法定义在这个类。

      +
      /**
      +    * Bind swoole event and start swoole server
      +    *
      +    * @throws ServerException
      +    * @throws Swoft\Bean\Exception\ContainerException
      +    */
      +protected function startSwoole(): void
      +{
      +    if (!$this->swooleServer) {
      +        throw new ServerException('You must to new server before start swoole!');
      +    }
      +
      +    // Always enable coroutine hook on server
      +    CLog::info('Swoole\Runtime::enableCoroutine');
      +    Runtime::enableCoroutine();
      +
      +    Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);
      +
      +    // Set settings
      +    $this->swooleServer->set($this->setting);
      +    // Update setting property
      +    // $this->setSetting($this->swooleServer->setting);
      +
      +    // Before Add event
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);
      +
      +    // Register events
      +    $defaultEvents = $this->defaultEvents();
      +    $swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +    // Add events
      +    $this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +
      +    //After add event
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);
      +
      +    // Before listener
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);
      +
      +    // Add port listener
      +    $this->addListener();
      +
      +    // Before bind process
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);
      +
      +    // Add Process
      +    Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);
      +
      +    // After bind process
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);
      +
      +    // Trigger event
      +    Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));
      +
      +    // Storage server instance
      +    self::$server = $this;
      +
      +    // Start swoole server
      +    $this->swooleServer->start();
      +}
      +
      $this->swooleServer->set($this->setting);
      +

      设置Swoole运行配置。

      +
      // Register events
      +$defaultEvents = $this->defaultEvents();
      +$swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +// Add events
      +$this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +

      添加Swoole回调事件。

      +
      // Add port listener
      +$this->addListener();
      +

      监听端口。

      +
      // Start swoole server
      +$this->swooleServer->start();
      +

      启动Swoole\Http\Server服务。

      +

      现在服务已经启动了,那http请求是怎么被处理的呢?

      +

      这个我们下一篇再继续讲。

      +]]>
      +
      + + Swoft 框架运行分析(四) —— EventProcessor模块分析 + https://liudon.com/posts/swoft-event-processor-analysis/ + Thu, 26 Sep 2019 13:02:18 +0800 + https://liudon.com/posts/swoft-event-processor-analysis/ + <p>今天我们来看一下<code>EventProcessor</code>的实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle event register +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeEvent()) { +</span></span><span style="display:flex;"><span> CLog::warning(<span style="color:#e6db74">&#39;Stop event processor by beforeEvent return false&#39;</span>); +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/**</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> EventManager <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">=</span> bean(<span style="color:#e6db74">&#39;eventManager&#39;</span>); +</span></span><span style="display:flex;"><span> [<span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2] <span style="color:#f92672">=</span> ListenerRegister::register(<span style="color:#f92672">$</span>eventManager); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Event manager initialized(</span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> listener, </span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> subscriber)&#39;</span>, <span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> Trigger a app init event +</span></span><span style="display:flex;"><span> Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterEvent(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>获取<code>eventManager</code>的<code>Bean</code>实例,对应为<code>Swoft\Event\Manager\EventManager</code>类。</p> + 今天我们来看一下EventProcessor的实现。

      +
      /**
      +    * Handle event register
      +    * @return bool
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeEvent()) {
      +        CLog::warning('Stop event processor by beforeEvent return false');
      +        return false;
      +    }
      +
      +    /** @var EventManager $eventManager */
      +    $eventManager = bean('eventManager');
      +    [$count1, $count2] = ListenerRegister::register($eventManager);
      +
      +    CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2);
      +
      +    // Trigger a app init event
      +    Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +
      +    return $this->application->afterEvent();
      +}
      +

      获取eventManagerBean实例,对应为Swoft\Event\Manager\EventManager类。

      +
      [$count1, $count2] = ListenerRegister::register($eventManager);
      +

      调用ListenerRegister类的register方法。

      +
      /**
      +    * @param EventManager $em
      +    *
      +    * @return array
      +    */
      +public static function register(EventManager $em): array
      +{
      +    foreach (self::$listeners as $className => $eventInfo) {
      +        $listener = Swoft::getSingleton($className);
      +
      +        if (!$listener instanceof EventHandlerInterface) {
      +            throw new RuntimeException("The event listener class '{$className}' must be instanceof EventHandlerInterface");
      +        }
      +
      +        $em->addListener($listener, $eventInfo);
      +    }
      +
      +    foreach (self::$subscribers as $className) {
      +        $subscriber = Swoft::getSingleton($className);
      +        if (!$subscriber instanceof EventSubscriberInterface) {
      +            throw new RuntimeException("The event subscriber class '{$className}' must be instanceof EventSubscriberInterface");
      +        }
      +
      +        $em->addSubscriber($subscriber);
      +    }
      +
      +    $count1 = count(self::$listeners);
      +    $count2 = count(self::$subscribers);
      +    // Clear data
      +    self::$listeners = self::$subscribers = [];
      +
      +    return [$count1, $count2];
      +}
      +

      遍历ListenerRegister类下的$listeners$subscribers属性,绑定事件到eventManagerBean实例上。

      +

      这里的$listeners$subscribers是从哪里来的呢?

      +

      这里以http-server为例。

      +

      swoft-component-2.0.5\src\http-server\src\Listener目录下,存在下面三个文件。

      +
      AfterRequestListener.php
      +AppInitCompleteListener.php
      +BeforeRequestListener.php
      +

      这里我们以AppInitCompleteListener.php为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      可以看到这里通过@Listener(SwoftEvent::APP_INIT_COMPLETE),使用了Swoft\Event\Annotation\Mapping\Listener类注解,对应的注解解析类为Swoft\Event\Annotation\Parser\ListenerParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Event\Annotation\Parser;
      +
      +use Doctrine\Common\Annotations\AnnotationException;
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\ListenerRegister;
      +
      +/**
      + * Class ListenerParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(Listener::class)
      + */
      +class ListenerParser extends Parser
      +{
      +    /**
      +     * @param int      $type
      +     * @param Listener $annotation
      +     *
      +     * @return array
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@Listener` must be defined on class!');
      +        }
      +
      +        // collect listeners
      +        ListenerRegister::addListener($this->className, [
      +            // event name => listener priority
      +            $annotation->getEvent() => $annotation->getPriority()
      +        ]);
      +
      +        return [$this->className, $this->className, Bean::SINGLETON, ''];
      +    }
      +}
      +
      /**
      +    * @param string $className
      +    * @param array  $definition [event name => listener priority]
      +    */
      +public static function addListener(string $className, array $definition = []): void
      +{
      +    // Collect listeners
      +    self::$listeners[$className] = $definition;
      +}
      +

      可以看到这里通过ListenerRegister::addListener方法,往ListenerRegister上注册了$listeners属性。

      +

      属性$listeners$subscribers的值,都是通过注解解析得来。

      +

      这里我们回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      trigger的方法定义如下。

      +
      /**
      +    * Trigger an swoft application event
      +    *
      +    * @param string|EventInterface $event eg: 'app.start' 'app.stop'
      +    * @param null|mixed            $target
      +    * @param array                 $params
      +    *
      +    * @return EventInterface
      +    */
      +public static function trigger($event, $target = null, ...$params): EventInterface
      +{
      +    /** @see EventManager::trigger() */
      +    return BeanFactory::getSingleton('eventManager')->trigger($event, $target, $params);
      +}
      +

      这里调用了eventManager这个Bean实例的trigger方法。

      +
      /**
      +    * Trigger an event. Can accept an EventInterface or will create one if not passed
      +    *
      +    * @param string|EventInterface $event  'app.start' 'app.stop'
      +    * @param mixed|string          $target It is object or string.
      +    * @param array|mixed           $args
      +    *
      +    * @return EventInterface
      +    * @throws InvalidArgumentException
      +    */
      +public function trigger($event, $target = null, array $args = []): EventInterface
      +{
      +    if ($isString = is_string($event)) {
      +        $name = trim($event);
      +    } elseif ($event instanceof EventInterface) {
      +        $name = trim($event->getName());
      +    } else {
      +        throw new InvalidArgumentException('Invalid event params for trigger event handler');
      +    }
      +
      +    $shouldCall = [];
      +
      +    // Have matched listener
      +    if (isset($this->listenedEvents[$name])) {
      +        $shouldCall[$name] = '';
      +    }
      +
      +    // Like 'app.db.query' => prefix: 'app.db'
      +    if ($pos = strrpos($name, '.')) {
      +        $prefix = substr($name, 0, $pos);
      +
      +        // Have a wildcards listener. eg 'app.db.*'
      +        $wildcardEvent = $prefix . '.*';
      +        if (isset($this->listenedEvents[$wildcardEvent])) {
      +            $shouldCall[$wildcardEvent] = substr($name, $pos + 1);
      +        }
      +    }
      +
      +    // Not found listeners
      +    if (!$shouldCall) {
      +        return $isString ? $this->basicEvent : $event;
      +    }
      +
      +    /** @var EventInterface $event */
      +    if ($isString) {
      +        $event = $this->events[$name] ?? $this->basicEvent;
      +    }
      +
      +    // Initial value
      +    $event->setName($name);
      +    $event->setParams($args);
      +    $event->setTarget($target);
      +    $event->stopPropagation(false);
      +
      +    // Notify event listeners
      +    foreach ($shouldCall as $name => $method) {
      +        $this->triggerListeners($this->listeners[$name], $event, $method);
      +
      +        if ($event->isPropagationStopped()) {
      +            return $event;
      +        }
      +    }
      +
      +    // Have global wildcards '*' listener.
      +    if (isset($this->listenedEvents['*'])) {
      +        $this->triggerListeners($this->listeners['*'], $event);
      +    }
      +
      +    return $event;
      +}
      +

      如果存在对应的事件,调用triggerListeners方法。

      +
      /**
      +    * @param array|ListenerQueue $listeners
      +    * @param EventInterface      $event
      +    * @param string              $method
      +    */
      +protected function triggerListeners($listeners, EventInterface $event, string $method = ''): void
      +{
      +    // $handled = false;
      +    $name     = $event->getName();
      +    $callable = false === strpos($name, '.');
      +
      +    // 循环调用监听器,处理事件
      +    foreach ($listeners as $listener) {
      +        if ($event->isPropagationStopped()) {
      +            break;
      +        }
      +
      +        if (is_object($listener)) {
      +            if ($listener instanceof EventHandlerInterface) {
      +                $listener->handle($event);
      +            } elseif ($method && method_exists($listener, $method)) {
      +                $listener->$method($event);
      +            } elseif ($callable && method_exists($listener, $name)) {
      +                $listener->$name($event);
      +            } elseif (method_exists($listener, '__invoke')) {
      +                $listener($event);
      +            }
      +        } elseif (is_callable($listener)) {
      +            $listener($event);
      +        }
      +    }
      +}
      +

      遍历事件回调,执行对应方法。

      +

      回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      这里的事件为SwoftEvent::APP_INIT_COMPLETE,所以这里会执行这个事件下的所有回调。

      +

      这里以Swoft\Http\Server\Listener\AppInitCompleteListener为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      这里使用了Swoft\Event\Annotation\Mapping\Listener注解,对应的事件为SwoftEvent::APP_INIT_COMPLETE

      +

      按照上面的分析,这里会调用到AppInitCompleteListenerhandle方法,获取httpRouterBean实例,注册http服务的路由信息和中间件。

      +

      到这里,我们大概清楚了EventProcessor这个模块的作用,注册了所有事件的回调。

      +]]>
      +
      + + 一个git submodule update引发的问题 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + Fri, 06 Sep 2019 15:13:51 +0800 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + <h4 id="背景">背景</h4> +<p>1月份的时候,用<code>hugo</code>搭了这套博客系统。</p> +<p>本机写md文件,更新到<code>github</code>,然后通过<code>travis-ci</code>自动发布。</p> +<p>jane主题是通过<code>git submodule</code>引入的,<code>.gitmodules</code>文件内容。</p> + 背景 +

      1月份的时候,用hugo搭了这套博客系统。

      +

      本机写md文件,更新到github,然后通过travis-ci自动发布。

      +

      jane主题是通过git submodule引入的,.gitmodules文件内容。

      +
      [submodule "themes/jane"]
      +	path = themes/jane
      +	url = https://github.com/xianmin/hugo-theme-jane.git
      +

      问题

      +

      最近几天更新完文章后,发现首页显示出了问题。

      +

      一开始以为是主题有问题,具体描述见首页文章不显示了

      +

      issue里: +shaform提到使用的并不是最新的版本。 +RocFang提到是git submodule使用的问题。

      +

      但是travis-ci每次都是通过git submodule update --init --recursive更新子仓库代码的,为什么会不是最新的代码呢。

      +

      问题重现

      +

      接下来,我们用一个新的仓库,来模拟重现一下。

      +
        +
      1. +

        克隆仓库。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +[root@VM_81_18_centos test]# 
        +
      2. +
      3. +

        添加文件。

        +
         [root@VM_81_18_centos xx]# cd test/
        + [root@VM_81_18_centos test]# echo "# test" >> README.md
        + [root@VM_81_18_centos test]# git add README.md
        + [root@VM_81_18_centos test]# 
        +
      4. +
      5. +

        引用子仓库。

        +
         [root@VM_81_18_centos test]# git submodule add git@github.com:xianmin/hugo-theme-jane.git theme/jane
        + Cloning into 'theme/jane'...
        + remote: Enumerating objects: 216, done.
        + remote: Counting objects: 100% (216/216), done.
        + remote: Compressing objects: 100% (128/128), done.
        + remote: Total 6165 (delta 102), reused 159 (delta 65), pack-reused 5949
        + Receiving objects: 100% (6165/6165), 3.05 MiB | 1.70 MiB/s, done.
        + Resolving deltas: 100% (3443/3443), done.
        +
      6. +
      7. +

        查看文件列表。

        +
         [root@VM_81_18_centos test]# ll
        + total 8
        + -rw-r--r-- 1 root root    5 Sep  6 16:05 README.md
        + drwxr-xr-x 7 root root 4096 Sep  6 16:08 typecho
        + [root@VM_81_18_centos test]# 
        +
      8. +
      9. +

        查看状态。

        +
        [root@VM_81_18_centos test]# git status
        +# On branch master
        +#
        +# Initial commit
        +#
        +# Changes to be committed:
        +#   (use "git rm --cached <file>..." to unstage)
        +#
        +#	new file:   .gitmodules
        +#	new file:   README.md
        +#	new file:   typecho
        +#
        +[root@VM_81_18_centos test]# 
        +
      10. +
      11. +

        查看修改。

        +
        [root@VM_81_18_centos test]# git diff --cached
        +diff --git a/.gitmodules b/.gitmodules
        +new file mode 100644
        +index 0000000..b1ddf70
        +--- /dev/null
        ++++ b/.gitmodules
        +@@ -0,0 +1,3 @@
        ++[submodule "typecho"]
        ++       path = typecho
        ++       url = https://github.com/Liudon/typecho
        +diff --git a/README.md b/README.md
        +new file mode 100644
        +index 0000000..9daeafb
        +--- /dev/null
        ++++ b/README.md
        +@@ -0,0 +1 @@
        ++test
        +diff --git a/typecho b/typecho
        +new file mode 160000
        +index 0000000..b0c4cc7
        +--- /dev/null
        ++++ b/typecho
        +@@ -0,0 +1 @@
        ++Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +[root@VM_81_18_centos test]# 
        +

        注意最后一行Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1

        +

        这个commitId是子仓库最新提交的记录id,对应的修改记录

        +
      12. +
      13. +

        提交修改。

        +
        [root@VM_81_18_centos test]# git push -u origin master
        +Counting objects: 4, done.
        +Compressing objects: 100% (3/3), done.
        +Writing objects: 100% (4/4), 362 bytes | 0 bytes/s, done.
        +Total 4 (delta 0), reused 0 (delta 0)
        +To git@github.com:Liudon/test.git
        +* [new branch]      master -> master
        +Branch master set up to track remote branch master from origin.
        +[root@VM_81_18_centos test]# 
        +

        + + +

        +

        提交后,在github上子仓库后面会多显示一个@xxxxx,这里就是引用的commitId,对应到前面git diff最后一行。

        +

        点击查看提交记录

        +

        + + +

        +

        本次提交的commitId5b11d515db8ad8d299ef1691f115590e0015c3b7,子仓库typecho单独记录了引入时的commitId,为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,对应的提交记录

        +
      14. +
      15. +

        接下来克隆子仓库,进行更新提交。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/typecho.git
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 1, done.
        +remote: Counting objects: 100% (1/1), done.
        +remote: Total 7179 (delta 0), reused 0 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7179/7179), 7.26 MiB | 2.02 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +[root@VM_81_18_centos xx]# 
        +[root@VM_81_18_centos xx]# cd typecho/
        +[root@VM_81_18_centos typecho]# git log -n 1
        +commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +Merge: c904005 8fd7492
        +Author: 祁宁 <magike.net@gmail.com>
        +Date:   Tue Nov 18 13:59:52 2014 +0800
        +
        +    Merge branch 'master' of https://github.com/typecho/typecho
        +[root@VM_81_18_centos typecho]#
        +

        通过git log,确认最新的提交commitId为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面的引入的一致。

        +
        [root@VM_81_18_centos typecho]# echo "xxx" > test
        +[root@VM_81_18_centos typecho]# 
        +[root@VM_81_18_centos typecho]# git add test
        +[root@VM_81_18_centos typecho]# git commit -m 'test'
        +[master 5dcc8f4] test
        +1 file changed, 1 insertion(+)
        +create mode 100644 test
        +[root@VM_81_18_centos typecho]# git push
        +Counting objects: 4, done.
        +Compressing objects: 100% (2/2), done.
        +Writing objects: 100% (3/3), 252 bytes | 0 bytes/s, done.
        +Total 3 (delta 1), reused 0 (delta 0)
        +remote: Resolving deltas: 100% (1/1), completed with 1 local object.
        +To git@github.com:Liudon/typecho.git
        +b0c4cc7..5dcc8f4  master -> master
        +[root@VM_81_18_centos typecho]# 
        +

        修改文件提交。

        +
        [root@VM_81_18_centos typecho]# git log -n 1
        +commit 5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2
        +Author: liudon <i.mu@qq.com>
        +Date:   Fri Sep 6 16:26:47 2019 +0800
        +
        +    test
        +[root@VM_81_18_centos typecho]#
        +

        最新提交的commitId5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2

        +
      16. +
      17. +

        重新克隆test库。

        +
        [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
        +Receiving objects: 100% (4/4), done.
        +[root@VM_81_18_centos yy]# cd test/
        +[root@VM_81_18_centos test]# ll
        +total 8
        +-rw-r--r-- 1 root root    5 Sep  6 16:31 README.md
        +drwxr-xr-x 2 root root 4096 Sep  6 16:31 typecho
        +[root@VM_81_18_centos test]# ll typecho/
        +total 0
        +[root@VM_81_18_centos test]# 
        +

        这里可以看到typecho目录下是没有文件的。

        +
        [root@VM_81_18_centos test]# git submodule update --init --recursive
        +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.26 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +Submodule path 'typecho': checked out 'b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1'
        +[root@VM_81_18_centos test]#
        +

        更新子仓库代码,这里可以看到最终checkout的版本为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面提交时的版本一致。

        +
      18. +
      +

      问题分析

      +

      git submodule add的时候,会记录当时引入时子仓库的版本id。

      +

      git submodule update --init --recursive,会检出引入时的仓库版本,这就是为啥代码没有更新了。

      +

      问题解决

      +
      [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
      +Cloning into 'test'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
      +Receiving objects: 100% (4/4), done.
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# cd test/
      +[root@VM_81_18_centos test]# ll
      +total 8
      +-rw-r--r-- 1 root root    5 Sep  6 16:37 README.md
      +drwxr-xr-x 2 root root 4096 Sep  6 16:37 typecho
      +[root@VM_81_18_centos test]# ll typecho/
      +total 0
      +[root@VM_81_18_centos test]# 
      +[root@VM_81_18_centos test]# git submodule update --init --remote --recursive
      +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
      +Cloning into 'typecho'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
      +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.24 MiB/s, done.
      +Resolving deltas: 100% (4844/4844), done.
      +Submodule path 'typecho': checked out '5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2'
      +[root@VM_81_18_centos test]#
      +

      使用git submodule update --init --remote --recursive命令。

      +]]>
      +
      + + 一个Curl的耗时长的问题 + https://liudon.com/posts/curl-cost-time-long/ + Wed, 04 Sep 2019 11:07:46 +0800 + https://liudon.com/posts/curl-cost-time-long/ + <p>发现某个接口请求很慢,但是后端确认接口是很快的。</p> +<p>在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。</p> +<p>业务里用到了<code>Requests</code>这个库,一开始以为是这个库导致的问题。</p> + 发现某个接口请求很慢,但是后端确认接口是很快的。

      +

      在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。

      +

      业务里用到了Requests这个库,一开始以为是这个库导致的问题。

      +

      Requests_Transport_cURL类里断点定位了下,确实很慢,curl_getinfo返回的信息如下。

      +
      array (
      +  'url' => 'http://xxxxx',
      +  'content_type' => 'text/html',
      +  'http_code' => 200,
      +  'header_size' => 64,
      +  'request_size' => 305,
      +  'filetime' => -1,
      +  'ssl_verify_result' => 0,
      +  'redirect_count' => 0,
      +  'total_time' => 2.074094,
      +  'namelookup_time' => 2.5E-5,
      +  'connect_time' => 0.032107,
      +  'pretransfer_time' => 0.032109,
      +  'size_upload' => 186,
      +  'size_download' => 99,
      +  'speed_download' => 47,
      +  'speed_upload' => 89,
      +  'download_content_length' => 99,
      +  'upload_content_length' => 186,
      +  'starttransfer_time' => 2.032866,
      +  'redirect_time' => 0,
      +  'certinfo' =>
      +  array (
      +  ),
      +)
      +

      这里可以看到starttransfer_time时间很长。

      +

      搜索了一番,发现网上一个case,cURL slow starttransfer_time

      +

      里面提供了Expect: 100-continue这个header,又搜索了一番这个header资料。

      +

      curl在发POST请求的时候,如果body大于1k:

      +
        +
      1. 先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
      2. +
      3. 如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server; +如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。
      4. +
      +

      在机器上抓了个包,执行下面命令。

      +
      注意,下面port后面的80改成实际的端口
      +
      +tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
      +

      拿到的包信息。

      +
      09:17:19.421587 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306
      +E..f.m@.@...d}@.        A...XF.-.@...h....s.......
      +....T.0TPOST /cgi HTTP/1.1
      +User-Agent: php-requests/1.6
      +Accept: */*
      +Accept-Encoding: deflate, gzip
      +Referer: http://xxxxx:12345/cgi
      +Content-Length: 188
      +Expect: 100-continue
      +Content-Type: multipart/form-data; boundary=----------------------------ee2f4d848646
      +
      +
      +09:17:21.421786 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188
      +E....n@.@..Md}@.        A...XF.-.B...h....s./.....
      +....T.0[------------------------------ee2f4d848646
      +Content-Disposition: form-data; name="req"
      +
      +{"command":"zzz","appId":"yyyy"}
      +------------------------------ee2f4d848646--
      +
      +09:17:21.458628 IP xxxxx:12345 > xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117
      +E...X.@.5.Q2    A..d}@.F..X..h.-.B......3.....
      +T.2Q....HTTP/1.1 200 OK
      +Content-Type: text/html
      +Content-Length: 53
      +
      +{
      +    "data": [],
      +    "errno": 0,
      +    "error": "ok"
      +}
      +

      可以看到确实是先发了一个100-continue的请求,然后再发的实际POST请求。

      +

      在机器上执行下面的shell命令。

      +
      curl 'http://xxxxx:12345/cgi' -H"Expect: 100-continue" -v
      +

      返回如下,可以看到返回的header头里确实没有Expect这项。

      +
      * About to connect() to xxxxx port 12345 (#0)
      +*   Trying xxxxx...
      +* Connected to xxxxx (xxxxx) port 12345 (#0)
      +> GET /cloud_cgi HTTP/1.1
      +> User-Agent: curl/7.29.0
      +> Host: xxxxx:12345
      +> Accept: */*
      +> Expect: 100-continue
      +> 
      +< HTTP/1.1 200 OK
      +< Content-Type: text/html
      +< Content-Length: 42
      +< 
      +* Connection #0 to host xxxxx left intact
      +{"errno":100,"error":"参数格式错误"}
      +

      解决方法:

      +

      请求的时候,header里新增一项。

      +
      Expect:
      +
      ]]>
      +
      + + Swoft 框架运行分析(三) —— BeanProcessor模块分析 + https://liudon.com/posts/swoft-bean-processor-analysis/ + Mon, 02 Sep 2019 18:29:06 +0800 + https://liudon.com/posts/swoft-bean-processor-analysis/ + <p>今天讲一下<code>BeanProcessor</code>模块,先看一下<code>handle</code>方法实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle bean +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws ReflectionException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws AnnotationException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeBean()) { +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>handler <span style="color:#f92672">=</span> new BeanHandler(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>definitions <span style="color:#f92672">=</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>getDefinitions(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>parsers <span style="color:#f92672">=</span> AnnotationRegister::getParsers(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>annotations <span style="color:#f92672">=</span> AnnotationRegister::getAnnotations(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> BeanFactory::addDefinitions(<span style="color:#f92672">$</span>definitions); +</span></span><span style="display:flex;"><span> BeanFactory::addAnnotations(<span style="color:#f92672">$</span>annotations); +</span></span><span style="display:flex;"><span> BeanFactory::addParsers(<span style="color:#f92672">$</span>parsers); +</span></span><span style="display:flex;"><span> BeanFactory::setHandler(<span style="color:#f92672">$</span>handler); +</span></span><span style="display:flex;"><span> BeanFactory::init(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> Config <span style="color:#f92672">$</span>config<span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>config <span style="color:#f92672">=</span> BeanFactory::getBean(<span style="color:#e6db74">&#39;config&#39;</span>); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config path=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getPath()); +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config env=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getEnv()); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>stats <span style="color:#f92672">=</span> BeanFactory::getStats(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Bean is initialized(</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">)&#39;</span>, SwoftHelper::formatStats(<span style="color:#f92672">$</span>stats)); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterBean(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>先通过<code>getDefinitions</code>方法获取所有的Bean定义。</p> + 今天讲一下BeanProcessor模块,先看一下handle方法实现。

      +
      /**
      +    * Handle bean
      +    *
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws AnnotationException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeBean()) {
      +        return false;
      +    }
      +
      +    $handler     = new BeanHandler();
      +    $definitions = $this->getDefinitions();
      +    $parsers     = AnnotationRegister::getParsers();
      +    $annotations = AnnotationRegister::getAnnotations();
      +
      +    BeanFactory::addDefinitions($definitions);
      +    BeanFactory::addAnnotations($annotations);
      +    BeanFactory::addParsers($parsers);
      +    BeanFactory::setHandler($handler);
      +    BeanFactory::init();
      +
      +    /* @var Config $config*/
      +    $config = BeanFactory::getBean('config');
      +
      +    CLog::info('config path=%s', $config->getPath());
      +    CLog::info('config env=%s', $config->getEnv());
      +
      +    $stats = BeanFactory::getStats();
      +
      +    CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats));
      +
      +    return $this->application->afterBean();
      +}
      +

      先通过getDefinitions方法获取所有的Bean定义。

      +
      /**
      +    * Get bean definitions
      +    *
      +    * @return array
      +    */
      +private function getDefinitions(): array
      +{
      +    // Core beans
      +    $definitions = [];
      +    $autoLoaders = AnnotationRegister::getAutoLoaders();
      +
      +    // get disabled loaders by application
      +    $disabledLoaders = $this->application->getDisabledAutoLoaders();
      +
      +    foreach ($autoLoaders as $autoLoader) {
      +        if (!$autoLoader instanceof DefinitionInterface) {
      +            continue;
      +        }
      +
      +        $loaderClass = get_class($autoLoader);
      +
      +        // If the component is disabled by user.
      +        if (isset($disabledLoaders[$loaderClass])) {
      +            CLog::info('Auto loader(%s) is <cyan>disabled</cyan>, skip handle it', $loaderClass);
      +            continue;
      +        }
      +
      +        // If the component is not enabled.
      +        if ($autoLoader instanceof ComponentInterface && !$autoLoader->isEnable()) {
      +            continue;
      +        }
      +
      +        $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());
      +    }
      +
      +    // Bean definitions
      +    $beanFile = $this->application->getBeanFile();
      +    $beanFile = alias($beanFile);
      +
      +    if (!file_exists($beanFile)) {
      +        throw new InvalidArgumentException(
      +            sprintf('The bean config file of %s is not exist!', $beanFile)
      +        );
      +    }
      +
      +    $beanDefinitions = require $beanFile;
      +    $definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +
      +    return $definitions;
      +}
      +

      通过AnnotationRegister::getAutoLoaders()拿到所有的autoloader对象,排除掉非DefinitionInterface对象,通过bean()方法获取定义的Bean信息。

      +

      这里以http-server\src\AutoLoader.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server;
      +
      +use function bean;
      +use function dirname;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Helper\ComposerJSON;
      +use Swoft\Http\Message\ContentType;
      +use Swoft\Http\Message\Response;
      +use Swoft\Http\Server\Formatter\HtmlResponseFormatter;
      +use Swoft\Http\Server\Formatter\JsonResponseFormatter;
      +use Swoft\Http\Server\Formatter\XmlResponseFormatter;
      +use Swoft\Http\Server\Parser\JsonRequestParser;
      +use Swoft\Http\Server\Parser\XmlRequestParser;
      +use Swoft\Http\Server\Swoole\RequestListener;
      +use Swoft\Server\SwooleEvent;
      +use Swoft\SwoftComponent;
      +
      +/**
      + * Class AutoLoader
      + *
      + * @since 2.0
      + */
      +class AutoLoader extends SwoftComponent
      +{
      +    /**
      +     * Metadata information for the component.
      +     *
      +     * @return array
      +     * @see ComponentInterface::getMetadata()
      +     */
      +    public function metadata(): array
      +    {
      +        $jsonFile = dirname(__DIR__) . '/composer.json';
      +
      +        return ComposerJSON::open($jsonFile)->getMetadata();
      +    }
      +
      +    /**
      +     * Get namespace and dirs
      +     *
      +     * @return array
      +     */
      +    public function getPrefixDirs(): array
      +    {
      +        return [
      +            __NAMESPACE__ => __DIR__,
      +        ];
      +    }
      +
      +    /**
      +     * @return array
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function beans(): array
      +    {
      +        return [
      +            'httpRequest'     => [
      +                'parsers' => [
      +                    ContentType::XML  => bean(XmlRequestParser::class),
      +                    ContentType::JSON => bean(JsonRequestParser::class),
      +                ]
      +            ],
      +            'httpResponse'    => [
      +                'format'     => Response::FORMAT_JSON,
      +                'formatters' => [
      +                    Response::FORMAT_HTML => bean(HtmlResponseFormatter::class),
      +                    Response::FORMAT_JSON => bean(JsonResponseFormatter::class),
      +                    Response::FORMAT_XML  => bean(XmlResponseFormatter::class),
      +                ]
      +            ],
      +            'acceptFormatter' => [
      +                'formats' => [
      +                    ContentType::JSON => Response::FORMAT_JSON,
      +                    ContentType::HTML => Response::FORMAT_HTML,
      +                    ContentType::XML  => Response::FORMAT_XML,
      +                ]
      +            ],
      +            'httpServer'      => [
      +                'on' => [
      +                    SwooleEvent::REQUEST => bean(RequestListener::class)
      +                ]
      +            ],
      +            'httpRouter'      => [
      +                'name'            => 'swoft-http-router',
      +                // config
      +                'ignoreLastSlash' => true,
      +                'tmpCacheNumber'  => 500,
      +            ],
      +        ];
      +    }
      +}
      +

      可以看到,这里通过beans()定义了httpRequesthttpResponseacceptFormatterhttpServerhttpRouter四个Bean对象。

      +

      回到上面getDefinitions方法。

      +

      $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());

      +

      然后将Bean信息添加到definitions对象上。

      +

      之后通过$beanFile = $this->application->getBeanFile();获取bean配置文件。

      +
      $beanDefinitions = require $beanFile;
      +$definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +

      加载配置文件,然后将Bean信息添加到definitions对象上。

      +

      可以看到Bean有两种定义方式:通过AutoLoader和配置文件,与swoft官方文档里的说明一致。

      +

      回到handle方法。

      +
      $parsers     = AnnotationRegister::getParsers();
      +$annotations = AnnotationRegister::getAnnotations();
      +

      还记得上一篇文章最后提到的AnnotationRegister类的annotationsparsers两个属性吗?这里通过getParsersgetAnnotations获取这两个属性。

      +
      BeanFactory::addDefinitions($definitions);
      +BeanFactory::addAnnotations($annotations);
      +BeanFactory::addParsers($parsers);
      +BeanFactory::setHandler($handler);
      +BeanFactory::init();
      +

      向BeanFatory注册信息。

      +
      /**
      +    * Init
      +    *
      +    * @return void
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function init(): void
      +{
      +    Container::getInstance()->init();
      +}
      +
      +...
      +
      +/**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public static function addDefinitions(array $definitions): void
      +{
      +    Container::getInstance()->addDefinitions($definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public static function addAnnotations(array $annotations): void
      +{
      +    Container::getInstance()->addAnnotations($annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public static function addParsers(array $annotationParsers): void
      +{
      +    Container::getInstance()->addParsers($annotationParsers);
      +}
      +
      +/**
      +    * Set bean handler
      +    *
      +    * @param HandlerInterface $handler
      +    */
      +public static function setHandler(HandlerInterface $handler): void
      +{
      +    Container::getInstance()->setHandler($handler);
      +}
      +

      这里可以看到所有的方法,最终都调用的是Swoft\Bean\Container类。

      +
      /**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public function addDefinitions(array $definitions): void
      +{
      +    $this->definitions = ArrayHelper::merge($this->definitions, $definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public function addAnnotations(array $annotations): void
      +{
      +    $this->annotations = ArrayHelper::merge($this->annotations, $annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public function addParsers(array $annotationParsers): void
      +{
      +    $this->parsers = ArrayHelper::merge($this->parsers, $annotationParsers);
      +}
      +
      +
      +/**
      +    * @param HandlerInterface $handler
      +    */
      +public function setHandler(HandlerInterface $handler): void
      +{
      +    $this->handler = $handler;
      +}
      +

      这四个方法就是注册属性,接下来是重头戏init方法。

      +
      /**
      +    * Init
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function init(): void
      +{
      +    // Parse annotations
      +    $this->parseAnnotations();
      +
      +    // Parse definitions
      +    $this->parseDefinitions();
      +
      +    // Init beans
      +    $this->initializeBeans();
      +}
      +

      先看parseAnnotations方法,从代码注释上也可以看出大概,解析注解,接下来我们看下具体是如何实现的。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseAnnotations(): void
      +{
      +    $annotationParser = new AnnotationObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +    $annotationData   = $annotationParser->parseAnnotations($this->annotations, $this->parsers);
      +
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
      +}
      +

      声明了一个AnnotationObjParser对象,调用了parseAnnotations方法。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @param array $annotations
      +    * @param array $parsers
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    */
      +public function parseAnnotations(array $annotations, array $parsers): array
      +{
      +    $this->parsers     = $parsers;
      +    $this->annotations = $annotations;
      +
      +    foreach ($this->annotations as $loadNameSpace => $classes) {
      +        foreach ($classes as $className => $classOneAnnotations) {
      +            $this->parseOneClassAnnotations($className, $classOneAnnotations);
      +        }
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      这里遍历所有的annotation类,循环调用parseOneClassAnnotations进行解析。

      +
      /**
      +    * Parse class all annotations
      +    *
      +    * @param string $className
      +    * @param array  $classOneAnnotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
      +{
      +    // Check class annotation tag
      +    if (!isset($classOneAnnotations['annotation'])) {
      +        throw new AnnotationException(
      +            sprintf('Property or method(%s) with `@xxx` must be define class annotation', $className)
      +        );
      +    }
      +
      +    // Parse class annotations
      +    $classAnnotations = $classOneAnnotations['annotation'];
      +    $reflectionClass  = $classOneAnnotations['reflection'];
      +
      +    $classAry = [
      +        $className,
      +        $reflectionClass,
      +        $classAnnotations
      +    ];
      +
      +    $objectDefinition = $this->parseClassAnnotations($classAry);
      +
      +    // Parse property annotations
      +    $propertyInjects        = [];
      +    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
      +    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
      +        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
      +        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
      +        if ($propertyInject) {
      +            $propertyInjects[$propertyName] = $propertyInject;
      +        }
      +    }
      +
      +    // Parse method annotations
      +    $methodInjects        = [];
      +    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
      +    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
      +        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];
      +
      +        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
      +        if ($methodInject) {
      +            $methodInjects[$methodName] = $methodInject;
      +        }
      +    }
      +
      +    if (!$objectDefinition) {
      +        return;
      +    }
      +
      +    if (!empty($propertyInjects)) {
      +        $objectDefinition->setPropertyInjections($propertyInjects);
      +    }
      +
      +    if (!empty($methodInjects)) {
      +        $objectDefinition->setMethodInjections($methodInjects);
      +    }
      +
      +    // Object definition and class name
      +    $name         = $objectDefinition->getName();
      +    $aliase       = $objectDefinition->getAlias();
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $name;
      +
      +    $this->classNames[$className]   = array_unique($classNames);
      +    $this->objectDefinitions[$name] = $objectDefinition;
      +
      +    if (!empty($aliase)) {
      +        $this->aliases[$aliase] = $name;
      +    }
      +}
      +

      这里可以看到分别有类注解、属性注解和方法注解三类。

      +

      对应官方文档的注解说明

      +
      /**
      +    * @param array $classAry
      +    *
      +    * @return ObjectDefinition|null
      +    */
      +private function parseClassAnnotations(array $classAry): ?ObjectDefinition
      +{
      +    [, , $classAnnotations] = $classAry;
      +
      +    $objectDefinition = null;
      +    foreach ($classAnnotations as $annotation) {
      +        $annotationClass = get_class($annotation);
      +        if (!isset($this->parsers[$annotationClass])) {
      +            continue;
      +        }
      +
      +        $parserClassName  = $this->parsers[$annotationClass];
      +        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
      +
      +        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
      +        if (empty($data)) {
      +            continue;
      +        }
      +
      +        if (count($data) !== 4) {
      +            throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
      +        }
      +
      +        [$name, $className, $scope, $alias] = $data;
      +        $name = empty($name) ? $className : $name;
      +
      +        if (empty($className)) {
      +            throw new InvalidArgumentException(sprintf('%s with class name can not be empty', $annotationClass));
      +        }
      +
      +        // Multiple coverage
      +        $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
      +    }
      +
      +    return $objectDefinition;
      +}
      +

      类注解,这里会调用对应解析类的parse方法。

      +

      这里以websocket-server\src\Annotation\Mapping\WsModule.phpwebsocket-server\src\Annotation\Parser\WsModuleParser.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Mapping;
      +
      +use Doctrine\Common\Annotations\Annotation\Attribute;
      +use Doctrine\Common\Annotations\Annotation\Attributes;
      +use Doctrine\Common\Annotations\Annotation\Required;
      +use Doctrine\Common\Annotations\Annotation\Target;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +
      +/**
      + * Class WebSocket - mark an websocket module handler class
      + *
      + * @since 2.0
      + *
      + * @Annotation
      + * @Target("CLASS")
      + * @Attributes(
      + *     @Attribute("name", type="string"),
      + *     @Attribute("path", type="string"),
      + *     @Attribute("controllers", type="array"),
      + *     @Attribute("messageParser", type="string"),
      + * )
      + */
      +final class WsModule
      +{
      +    /**
      +     * Websocket route path.(it must unique in a application)
      +     *
      +     * @var string
      +     * @Required()
      +     */
      +    private $path = '/';
      +
      +    /**
      +     * Module name.
      +     *
      +     * @var string
      +     */
      +    private $name = '';
      +
      +    /**
      +     * Routing path params binding. eg. {"id"="\d+"}
      +     *
      +     * @var array
      +     */
      +    private $params = [];
      +
      +    /**
      +     * Message controllers of the module
      +     *
      +     * @var string[]
      +     */
      +    private $controllers = [];
      +
      +    /**
      +     * Message parser class for the module
      +     *
      +     * @var string
      +     */
      +    private $messageParser = RawTextParser::class;
      +
      +    /**
      +     * Default message command. Format 'controller.action'
      +     *
      +     * @var string
      +     */
      +    private $defaultCommand = 'home.index';
      +
      +    /**
      +     * Default message opcode for response. please see WEBSOCKET_OPCODE_*
      +     *
      +     * @var int
      +     */
      +    private $defaultOpcode = 0;
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $values
      +     */
      +    public function __construct(array $values)
      +    {
      +        if (isset($values['value'])) {
      +            $this->path = (string)$values['value'];
      +        } elseif (isset($values['path'])) {
      +            $this->path = (string)$values['path'];
      +        }
      +
      +        if (isset($values['name'])) {
      +            $this->name = (string)$values['name'];
      +        }
      +
      +        if (isset($values['params'])) {
      +            $this->params = (array)$values['params'];
      +        }
      +
      +        if (isset($values['controllers'])) {
      +            $this->controllers = (array)$values['controllers'];
      +        }
      +
      +        if (isset($values['messageParser'])) {
      +            $this->messageParser = $values['messageParser'];
      +        }
      +
      +        if (isset($values['defaultOpcode'])) {
      +            $this->defaultOpcode = (int)$values['defaultOpcode'];
      +        }
      +
      +        if (isset($values['defaultCommand'])) {
      +            $this->defaultCommand = $values['defaultCommand'];
      +        }
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getPath(): string
      +    {
      +        return $this->path;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getMessageParser(): string
      +    {
      +        return $this->messageParser;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getDefaultCommand(): string
      +    {
      +        return $this->defaultCommand;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getName(): string
      +    {
      +        return $this->name;
      +    }
      +
      +    /**
      +     * @return string[]
      +     */
      +    public function getControllers(): array
      +    {
      +        return $this->controllers;
      +    }
      +
      +    /**
      +     * @return array
      +     */
      +    public function getParams(): array
      +    {
      +        return $this->params;
      +    }
      +
      +    /**
      +     * @return int
      +     */
      +    public function getDefaultOpcode(): int
      +    {
      +        return $this->defaultOpcode;
      +    }
      +}
      +

      WsModule声明了一个类注解。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Stdlib\Helper\Str;
      +use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +use Swoft\WebSocket\Server\Router\RouteRegister;
      +
      +/**
      + * Class WebSocketParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(WsModule::class)
      + */
      +class WsModuleParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int      $type Class or Method or Property
      +     * @param WsModule $ann  Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $ann): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@WsModule` must be defined on class!');
      +        }
      +
      +        $class = $this->className;
      +
      +        RouteRegister::bindModule($class, [
      +            'path'           => $ann->getPath() ?: Str::getClassName($class, 'Module'),
      +            'name'           => $ann->getName(),
      +            'params'         => $ann->getParams(),
      +            'class'          => $class,
      +            'eventMethods'   => [],
      +            'controllers'    => $ann->getControllers(),
      +            'messageParser'  => $ann->getMessageParser() ?: RawTextParser::class,
      +            'defaultOpcode' => $ann->getDefaultOpcode(),
      +            'defaultCommand' => $ann->getDefaultCommand(),
      +        ]);
      +
      +        return [$class, $class, Bean::SINGLETON, ''];
      +    }
      +}
      +

      按上一篇文章说明,这里WsModuleParser会被标记为注解类WsModule的注解解析类。

      +

      解析注解的时候,会调用WsModuleParserparse方法,这里通过RouteRegister::bindModule做了一些路由操作,这里后续再讲,这里不做深入介绍。

      +

      属性和方法注解,也是类似的,parseAnnotations方法就讲完了。

      +

      回到Container类的init方法,接下来调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    */
      +private function parseDefinitions(): void
      +{
      +    $annotationParser = new DefinitionObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +
      +    // Collect info
      +    $definitionData = $annotationParser->parseDefinitions();
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $definitionData;
      +}
      +

      声明了一个DefinitionObjParser对象,调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    *
      +    * @return array
      +    */
      +public function parseDefinitions(): array
      +{
      +    foreach ($this->definitions as $beanName => $definition) {
      +        if (isset($this->objectDefinitions[$beanName])) {
      +            $objectDefinition = $this->objectDefinitions[$beanName];
      +            $this->resetObjectDefinition($beanName, $objectDefinition, $definition);
      +            continue;
      +        }
      +
      +        $this->createObjectDefinition($beanName, $definition);
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      遍历所有的Bean对象,调用createObjectDefinition方法。

      +
      /**
      +    * Create object definition for definition
      +    *
      +    * @param string $beanName
      +    * @param array  $definition
      +    */
      +private function createObjectDefinition(string $beanName, array $definition): void
      +{
      +    $className = $definition['class'] ?? '';
      +    if (empty($className)) {
      +        throw new InvalidArgumentException(sprintf('%s key for definition must be defined class', $beanName));
      +    }
      +
      +    $objDefinition = new ObjectDefinition($beanName, $className);
      +    $objDefinition = $this->updateObjectDefinitionByDefinition($objDefinition, $definition);
      +
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $beanName;
      +
      +    $this->classNames[$className]       = array_unique($classNames);
      +    $this->objectDefinitions[$beanName] = $objDefinition;
      +}
      +

      声明了ObjectDefinition对象,调用了updateObjectDefinitionByDefinition方法。

      +
      /**
      +    * Update definition
      +    *
      +    * @param ObjectDefinition $objDfn
      +    * @param array            $definition
      +    *
      +    * @return ObjectDefinition
      +    */
      +private function updateObjectDefinitionByDefinition(ObjectDefinition $objDfn, array $definition): ObjectDefinition
      +{
      +    [$constructInject, $propertyInjects, $option] = $this->parseDefinition($definition);
      +
      +    // Set construct inject
      +    if (!empty($constructInject)) {
      +        $objDfn->setConstructorInjection($constructInject);
      +    }
      +
      +    // Set property inject
      +    foreach ($propertyInjects as $propertyName => $propertyInject) {
      +        $objDfn->setPropertyInjection($propertyName, $propertyInject);
      +    }
      +
      +    $scopes = [
      +        Bean::SINGLETON,
      +        Bean::PROTOTYPE,
      +        Bean::REQUEST,
      +    ];
      +
      +    $scope = $option['scope'] ?? '';
      +    $alias = $option['alias'] ?? '';
      +
      +    if (!empty($scope) && !in_array($scope, $scopes, true)) {
      +        throw new InvalidArgumentException('Scope for definition is not undefined');
      +    }
      +
      +    // Update scope
      +    if (!empty($scope)) {
      +        $objDfn->setScope($scope);
      +    }
      +
      +    // Update alias
      +    if (!empty($alias)) {
      +        $objDfn->setAlias($alias);
      +
      +        $objAlias = $objDfn->getAlias();
      +        unset($this->aliases[$objAlias]);
      +
      +        $this->aliases[$alias] = $objDfn->getName();
      +    }
      +
      +    return $objDfn;
      +}
      +

      这里调用了parseDefinition方法进行解析。

      +
      /**
      +    * Parse definition
      +    *
      +    * @param array $definition
      +    *
      +    * @return array
      +    */
      +private function parseDefinition(array $definition): array
      +{
      +    // Remove class key
      +    unset($definition['class']);
      +
      +    // Parse construct
      +    $constructArgs = $definition[0] ?? [];
      +    if (!is_array($constructArgs)) {
      +        throw new InvalidArgumentException('Construct args for definition must be array');
      +    }
      +
      +    // Parse construct args
      +    $argInjects = [];
      +    foreach ($constructArgs as $arg) {
      +        [$argValue, $argIsRef] = $this->getValueByRef($arg);
      +
      +        $argInjects[] = new ArgsInjection($argValue, $argIsRef);
      +    }
      +
      +    // Set construct inject
      +    $constructInject = null;
      +    if (!empty($argInjects)) {
      +        $constructInject = new MethodInjection('__construct', $argInjects);
      +    }
      +
      +    // Remove construct definition
      +    unset($definition[0]);
      +
      +    // Parse definition option
      +    $option = $definition['__option'] ?? [];
      +    if (!is_array($option)) {
      +        throw new InvalidArgumentException('__option for definition must be array');
      +    }
      +
      +    // Remove `__option`
      +    unset($definition['__option']);
      +
      +    // Parse definition properties
      +    $propertyInjects = [];
      +    foreach ($definition as $propertyName => $propertyValue) {
      +        if (!is_string($propertyName)) {
      +            throw new InvalidArgumentException('Property key from definition must be string');
      +        }
      +
      +        [$proValue, $proIsRef] = $this->getValueByRef($propertyValue);
      +
      +        // Parse property for array
      +        if (is_array($proValue)) {
      +            $proValue = $this->parseArrayProperty($proValue);
      +        }
      +
      +        $propertyInject = new PropertyInjection($propertyName, $proValue, $proIsRef);
      +
      +        $propertyInjects[$propertyName] = $propertyInject;
      +    }
      +
      +    return [$constructInject, $propertyInjects, $option];
      +}
      +

      解析__construct方法和传参,解析属性信息。

      +

      回到updateObjectDefinitionByDefinition方法,将__construct和类属性信息注册到ObjectDefinition对象上,到这里parseDefinitions方法执行完毕。

      +

      回到Container类的init方法,接下来调用了initializeBeans方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @throws InvalidArgumentException
      +    * @throws ReflectionException
      +    */
      +private function initializeBeans(): void
      +{
      +    /* @var ObjectDefinition $objectDefinition */
      +    foreach ($this->objectDefinitions as $beanName => $objectDefinition) {
      +        $scope = $objectDefinition->getScope();
      +        // Exclude request
      +        if ($scope === Bean::REQUEST) {
      +            $this->requestDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // Exclude session
      +        if ($scope === Bean::SESSION) {
      +            $this->sessionDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // New bean
      +        $this->newBean($beanName);
      +    }
      +}
      +

      对于scope不为Bean::REQUESTBean::SESSION的,调用newBean方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object
      +    * @throws ReflectionException
      +    */
      +private function newBean(string $beanName, string $id = '')
      +{
      +    // First, check bean whether has been create.
      +    if (isset($this->singletonPool[$beanName]) || isset($this->prototypePool[$beanName])) {
      +        return $this->get($beanName);
      +    }
      +
      +    // Get object definition
      +    $objectDefinition = $this->getNewObjectDefinition($beanName);
      +
      +    $scope     = $objectDefinition->getScope();
      +    $alias     = $objectDefinition->getAlias();
      +    $className = $objectDefinition->getClassName();
      +
      +    // Cache reflection class info
      +    Reflections::cache($className);
      +
      +    // Before initialize bean
      +    $this->beforeInit($beanName, $className, $objectDefinition);
      +
      +    $constructArgs   = [];
      +    $constructInject = $objectDefinition->getConstructorInjection();
      +    if ($constructInject !== null) {
      +        $constructArgs = $this->getConstructParams($constructInject, $id);
      +    }
      +
      +    $propertyInjects = $objectDefinition->getPropertyInjections();
      +
      +    // Proxy class
      +    if ($this->handler) {
      +        $className = $this->handler->classProxy($className);
      +    }
      +
      +    $reflectionClass = new ReflectionClass($className);
      +    $reflectObject   = $this->newInstance($reflectionClass, $constructArgs);
      +
      +    // Inject properties values
      +    $this->newProperty($reflectObject, $reflectionClass, $propertyInjects, $id);
      +
      +    // Alias
      +    if (!empty($alias)) {
      +        $this->aliases[$alias] = $beanName;
      +    }
      +
      +    // Call init method if exist
      +    if ($reflectionClass->hasMethod(self::INIT_METHOD)) {
      +        $reflectObject->{self::INIT_METHOD}();
      +    }
      +
      +    return $this->setNewBean($beanName, $scope, $reflectObject, $id);
      +}
      +

      通过反射实例化Bean对应的类,注册对应的属性。

      +

      如果类存在self::INIT_METHOD方法,执行此方法。

      +
      /**
      +    * @param string $beanName
      +    * @param string $scope
      +    * @param object $object
      +    * @param string $id
      +    *
      +    * @return object
      +    */
      +private function setNewBean(string $beanName, string $scope, $object, string $id = '')
      +{
      +    switch ($scope) {
      +        case Bean::SINGLETON: // Singleton
      +            $this->singletonPool[$beanName] = $object;
      +            break;
      +        case Bean::PROTOTYPE:
      +            $this->prototypePool[$beanName] = $object;
      +            // Clone it
      +            $object = clone $object;
      +            break;
      +        case Bean::REQUEST:
      +            $this->requestPool[$id][$beanName] = $object;
      +            break;
      +        case Bean::SESSION:
      +            $this->sessionPool[$id][$beanName] = $object;
      +            break;
      +    }
      +
      +    return $object;
      +}
      +

      setNewBean方法,根据对应的scope信息,将实例化后的反射类注册到对应的类属性上。

      +

      到这里BeanProcessor类就执行完了。

      +]]>
      +
      + + Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + Thu, 29 Aug 2019 19:11:04 +0800 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + <p>上一篇介绍了,<code>SwoftApplication</code>里定义了6个Processor对象。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protected function processors(): array +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> return [ +</span></span><span style="display:flex;"><span> new EnvProcessor($this), +</span></span><span style="display:flex;"><span> new ConfigProcessor($this), +</span></span><span style="display:flex;"><span> new AnnotationProcessor($this), +</span></span><span style="display:flex;"><span> new BeanProcessor($this), +</span></span><span style="display:flex;"><span> new EventProcessor($this), +</span></span><span style="display:flex;"><span> new ConsoleProcessor($this), +</span></span><span style="display:flex;"><span> ]; +</span></span><span style="display:flex;"><span> } +</span></span></code></pre></div><p>所有的Processor实现都在<code>framework\src\Processor</code>目录下。</p> + 上一篇介绍了,SwoftApplication里定义了6个Processor对象。

      +
      protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      所有的Processor实现都在framework\src\Processor目录下。

      +
        +
      1. +

        EnvProcessor,运行环境检查。

        +
      2. +
      3. +

        ConfigProcessor,配置相关。

        +
      4. +
      5. +

        AnnotationProcessor,注解相关。

        +
      6. +
      7. +

        BeanProcessor,Bean相关。

        +
      8. +
      9. +

        EventProcessor,事件相关。

        +
      10. +
      11. +

        ConsoleProcessor,命令行输入相关。

        +
      12. +
      +

      今天先讲一下AnnotationProcessor这个模块的实现。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Processor;
      +
      +use Exception;
      +use Swoft\Annotation\AnnotationRegister;
      +use Swoft\Log\Helper\CLog;
      +
      +/**
      + * Annotation processor
      + * @since 2.0
      + */
      +class AnnotationProcessor extends Processor
      +{
      +    /**
      +     * Handle annotation
      +     *
      +     * @return bool
      +     * @throws Exception
      +     */
      +    public function handle(): bool
      +    {
      +        if (!$this->application->beforeAnnotation()) {
      +            CLog::warning('Stop annotation processor by beforeAnnotation return false');
      +            return false;
      +        }
      +
      +        $app = $this->application;
      +
      +        // Find AutoLoader classes. Parse and collect annotations.
      +        AnnotationRegister::load([
      +            'inPhar'               => \IN_PHAR,
      +            'basePath'             => $app->getBasePath(),
      +            'notifyHandler'        => [$this, 'notifyHandler'],
      +            'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
      +            'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
      +        ]);
      +
      +        $stats = AnnotationRegister::getClassStats();
      +
      +        CLog::info(
      +            'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
      +            $stats['autoloader'],
      +            $stats['annotation'],
      +            $stats['parser']
      +        );
      +
      +        return $this->application->afterAnnotation();
      +    }
      +
      +    /**
      +     * @param string $type
      +     * @param string $target
      +     * @see \Swoft\Annotation\Resource\AnnotationResource::load()
      +     */
      +    public function notifyHandler(string $type, $target): void
      +    {
      +        switch ($type) {
      +            case 'excludeNs':
      +                CLog::debug('Exclude namespace %s', $target);
      +                break;
      +            case 'noLoaderFile':
      +                CLog::debug('No autoloader on %s', $target);
      +                break;
      +            case 'noLoaderClass':
      +                CLog::debug('Autoloader class not exist %s', $target);
      +                break;
      +            case 'findLoaderClass':
      +                CLog::debug('Find autoloader %s', $target);
      +                break;
      +            case 'addLoaderClass':
      +                CLog::debug('Parse autoloader %s', $target);
      +                break;
      +            case 'noExistClass':
      +                CLog::debug('Skip interface or trait %s', $target);
      +                break;
      +        }
      +    }
      +}
      +

      核心逻辑调用AnnotationRegister类的load方法,定义如下。

      +
      /**
      +    * Load annotation class
      +    *
      +    * @param array $config
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function load(array $config = []): void
      +{
      +    $resource = new AnnotationResource($config);
      +    $resource->load();
      +}
      +

      这里又调用了AnnotationResource类的load方法,定义如下。

      +
      /**
      +    * Load annotation resource by find ClassLoader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function load(): void
      +{
      +    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
      +
      +    foreach ($prefixDirsPsr4 as $ns => $paths) {
      +        // Only scan namespaces
      +        if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // It is excluded psr4 prefix
      +        if ($this->isExcludedPsr4Prefix($ns)) {
      +            AnnotationRegister::registerExcludeNs($ns);
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // Find package/component loader class
      +        foreach ($paths as $path) {
      +            $loaderFile = $this->getAnnotationClassLoaderFile($path);
      +            if (!file_exists($loaderFile)) {
      +                $this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
      +                continue;
      +            }
      +
      +            $loaderClass = $this->getAnnotationLoaderClassName($ns);
      +            if (!class_exists($loaderClass)) {
      +                $this->notify('noLoaderClass', $loaderClass);
      +                continue;
      +            }
      +
      +            $loaderObject = new $loaderClass();
      +            if (!$loaderObject instanceof LoaderInterface) {
      +                $this->notify('invalidLoader', $loaderFile);
      +                continue;
      +            }
      +
      +            $this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
      +
      +            // If is disable, will skip scan annotation classes
      +            if (!isset($this->disabledAutoLoaders[$loaderClass])) {
      +                AnnotationRegister::registerAutoLoaderFile($loaderFile);
      +                $this->notify('addLoaderClass', $loaderClass);
      +                $this->loadAnnotation($loaderObject);
      +            }
      +
      +            // Storage auto loader to register
      +            AnnotationRegister::addAutoLoader($ns, $loaderObject);
      +        }
      +    }
      +}
      +

      通过getPrefixesPsr4方法获取所有自动加载的命名空间和目录,遍历目录下的AutoLoader.php文件。

      +

      通过registerAutoLoaderFile注册自动加载文件到AnnotationRegister对象上。

      +

      然后调用了loadAnnotation方法,传入的是一个autoload对象。

      +
      /**
      +    * Load annotations from an component loader config.
      +    *
      +    * @param LoaderInterface $loader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function loadAnnotation(LoaderInterface $loader): void
      +{
      +    $nsPaths = $loader->getPrefixDirs();
      +
      +    foreach ($nsPaths as $ns => $path) {
      +        $iterator = DirectoryHelper::recursiveIterator($path);
      +
      +        /* @var SplFileInfo $splFileInfo */
      +        foreach ($iterator as $splFileInfo) {
      +            $filePath = $splFileInfo->getPathname();
      +            // $splFileInfo->isDir();
      +            if (is_dir($filePath)) {
      +                continue;
      +            }
      +
      +            $fileName  = $splFileInfo->getFilename();
      +            $extension = $splFileInfo->getExtension();
      +
      +            if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
      +                continue;
      +            }
      +
      +            // It is exclude filename
      +            if (isset($this->excludedFilenames[$fileName])) {
      +                AnnotationRegister::registerExcludeFilename($fileName);
      +                continue;
      +            }
      +
      +            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
      +            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
      +            $className = sprintf('%s%s', $ns, $pathName);
      +
      +            // Fix repeat included file bug
      +            $autoload = in_array($filePath, $this->includedFiles, true);
      +
      +            // Will filtering: interfaces and traits
      +            if (!class_exists($className, !$autoload)) {
      +                $this->notify('noExistClass', $className);
      +                continue;
      +            }
      +
      +            // Parse annotation
      +            $this->parseAnnotation($ns, $className);
      +        }
      +    }
      +}
      +

      通过getPrefixDirs获取当前命名空间的目录,然后通过recursiveIterator遍历目录下的文件。

      +

      排除目录和非.php结尾的文件,最后会调用parseAnnotation方法。

      +
      /**
      +    * Parser annotation
      +    *
      +    * @param string $namespace
      +    * @param string $className
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseAnnotation(string $namespace, string $className): void
      +{
      +    // Annotation reader
      +    $reflectionClass = new ReflectionClass($className);
      +
      +    // Fix ignore abstract
      +    if ($reflectionClass->isAbstract()) {
      +        return;
      +    }
      +    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
      +
      +    if (!empty($oneClassAnnotation)) {
      +        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
      +    }
      +}
      +

      这里调用了parseOneClassAnnotation方法。

      +
      /**
      +    * Parse an class annotation
      +    *
      +    * @param ReflectionClass $reflectionClass
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
      +{
      +    // Annotation reader
      +    $reader    = new AnnotationReader();
      +    $className = $reflectionClass->getName();
      +
      +    $oneClassAnnotation = [];
      +    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);
      +
      +    // Register annotation parser
      +    foreach ($classAnnotations as $classAnnotation) {
      +        if ($classAnnotation instanceof AnnotationParser) {
      +            $this->registerParser($className, $classAnnotation);
      +
      +            return [];
      +        }
      +    }
      +
      +    // Class annotation
      +    if (!empty($classAnnotations)) {
      +        $oneClassAnnotation['annotation'] = $classAnnotations;
      +        $oneClassAnnotation['reflection'] = $reflectionClass;
      +    }
      +
      +    // Property annotation
      +    $reflectionProperties = $reflectionClass->getProperties();
      +    foreach ($reflectionProperties as $reflectionProperty) {
      +        $propertyName        = $reflectionProperty->getName();
      +        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);
      +
      +        if (!empty($propertyAnnotations)) {
      +            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
      +            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
      +        }
      +    }
      +
      +    // Method annotation
      +    $reflectionMethods = $reflectionClass->getMethods();
      +    foreach ($reflectionMethods as $reflectionMethod) {
      +        $methodName        = $reflectionMethod->getName();
      +        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);
      +
      +        if (!empty($methodAnnotations)) {
      +            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
      +            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
      +        }
      +    }
      +
      +    $parentReflectionClass = $reflectionClass->getParentClass();
      +    if ($parentReflectionClass !== false) {
      +        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
      +        if (!empty($parentClassAnnotation)) {
      +            $oneClassAnnotation['parent'] = $parentClassAnnotation;
      +        }
      +    }
      +
      +    return $oneClassAnnotation;
      +}
      +

      这里就是解析注解了,可以看到分别有类注解、属性注解和方法注解三类。

      +

      这里注意这一段代码。

      +
      // Register annotation parser
      +foreach ($classAnnotations as $classAnnotation) {
      +    if ($classAnnotation instanceof AnnotationParser) {
      +        $this->registerParser($className, $classAnnotation);
      +
      +        return [];
      +    }
      +}
      +

      遍历注解类,如果注解属于AnnotationParser实例,这里调用registerParser进行注册。

      +
      /**
      +    * @param string $annotationClass
      +    * @param string $parserClassName
      +    */
      +public static function registerParser(string $annotationClass, string $parserClassName): void
      +{
      +    self::$classStats['parser']++;
      +    self::$parsers[$annotationClass] = $parserClassName;
      +}
      +

      回到上一个方法,解析完后,又调用了AnnotationRegister类的registerAnnotation方法进行注册。

      +
      /**
      +    * @param string $loadNamespace
      +    * @param string $className
      +    * @param array  $classAnnotation
      +    */
      +public static function registerAnnotation(string $loadNamespace, string $className, array $classAnnotation): void
      +{
      +    self::$classStats['annotation']++;
      +    self::$annotations[$loadNamespace][$className] = $classAnnotation;
      +}
      +

      至此,整个AnnotationProcessor加载完毕,这里AnnotationRegister类里会有annotationsparsers两个属性,这个信息在后面的BeanProcessor里还会用到。

      +]]>
      +
      + + Swoft 框架运行分析(一) + https://liudon.com/posts/swoft-execution-analysis/ + Thu, 29 Aug 2019 17:22:28 +0800 + https://liudon.com/posts/swoft-execution-analysis/ + <blockquote> +<p>Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。</p> +</blockquote> +<p>以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。</p> +<p>刚开始看的时候,感觉自己像个原始人,完全看不懂。</p> + +

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。

      + +

      以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。

      +

      刚开始看的时候,感觉自己像个原始人,完全看不懂。

      +

      官方文档没有介绍Swoft的实现,网上的一些文章跟当前版本代码已经不一致了。

      +

      自己花了一周时间,终于梳理清楚了,看完更觉得自己是个原始人了。

      +

      使用的框架组件版本为:

      +
      swoft-2.0.5
      +swoft-component-2.0.5
      +

      这里以Swoft启动http server为例。

      +
      php bin/swoft http:start
      +

      执行上述命令,启动http server。

      +

      这里执行的是bin/swoft文件。

      +
      #!/usr/bin/env php
      +<?php declare(strict_types=1);
      +
      +// Bootstrap
      +require_once __DIR__ . '/bootstrap.php';
      +
      +Swoole\Coroutine::set([
      +    'max_coroutine' => 300000,
      +]);
      +
      +// Run application
      +(new \App\Application())->run();
      +

      这里引入bootstrap.php文件,引入composer自动加载文件。

      +
      <?php
      +// Composer autoload
      +require_once dirname(__DIR__) . '/vendor/autoload.php';
      +

      然后执行Swoft\App\Application类下的run方法。

      +
      <?php declare(strict_types=1);
      +
      +namespace App;
      +
      +use Swoft\SwoftApplication;
      +use function date_default_timezone_set;
      +
      +/**
      + * Class Application
      + *
      + * @since 2.0
      + */
      +class Application extends SwoftApplication
      +{
      +    protected function beforeInit(): void
      +    {
      +        parent::beforeInit();
      +
      +        date_default_timezone_set('Asia/Shanghai');
      +    }
      +}
      +

      这里继承了Swoft\SwoftApplication类,这里只粘贴了部分代码。

      +
      /**
      + * Swoft application
      + *
      + * @since 2.0
      + */
      +class SwoftApplication implements SwoftInterface, ApplicationInterface
      +{
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $config
      +     */
      +    public function __construct(array $config = [])
      +    {
      +        // Check runtime env
      +        SwoftHelper::checkRuntime();
      +
      +        // Storage as global static property.
      +        Swoft::$app = $this;
      +
      +        // Before init
      +        $this->beforeInit();
      +
      +        // Init console logger
      +        $this->initCLogger();
      +
      +        // Can setting properties by array
      +        if ($config) {
      +            ObjectHelper::init($this, $config);
      +        }
      +
      +        // Init application
      +        $this->init();
      +
      +        CLog::info('Project path is <info>%s</info>', $this->basePath);
      +
      +        // After init
      +        $this->afterInit();
      +    }
      +
      +    protected function init(): void
      +    {
      +        // Init system path aliases
      +        $this->findBasePath();
      +        $this->setSystemAlias();
      +
      +        $processors = $this->processors();
      +
      +        $this->processor = new ApplicationProcessor($this);
      +        $this->processor->addFirstProcessor(...$processors);
      +    }
      +
      +    /**
      +     * Run application
      +     */
      +    public function run(): void
      +    {
      +        if (!$this->beforeRun()) {
      +            return;
      +        }
      +
      +        $this->processor->handle();
      +    }
      +
      +    /**
      +     * @return ProcessorInterface[]
      +     */
      +    protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      __construct方法里检查运行环境,初始化日志组件,然后调用了init方法。

      +

      init方法里声明了processor对象。

      +

      processors方法定义了Swoft框架的6个Processor对象。

      +

      run方法里直接调用processor对象的handler方法。

      +
      <?php
      +
      +namespace Swoft\Processor;
      +
      +use Swoft\Stdlib\Helper\ArrayHelper;
      +use function get_class;
      +
      +/**
      + * Application processor
      + * @since 2.0
      + */
      +class ApplicationProcessor extends Processor
      +{
      +    /**
      +     * @var ProcessorInterface[]
      +     */
      +    private $processors = [];
      +
      +    /**
      +     * Handle application processors
      +     */
      +    public function handle(): bool
      +    {
      +        $disabled = $this->application->getDisabledProcessors();
      +
      +        foreach ($this->processors as $processor) {
      +            $class = get_class($processor);
      +
      +            // If is disabled, skip handle.
      +            if (isset($disabled[$class])) {
      +                continue;
      +            }
      +
      +            $processor->handle();
      +        }
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add first processor
      +     *
      +     * @param Processor[] $processor
      +     * @return bool
      +     */
      +    public function addFirstProcessor(Processor ...$processor): bool
      +    {
      +        array_unshift($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add last processor
      +     *
      +     * @param Processor[] $processor
      +     *
      +     * @return bool
      +     */
      +    public function addLastProcessor(Processor ...$processor): bool
      +    {
      +        array_push($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add processors
      +     *
      +     * @param int         $index
      +     * @param Processor[] $processors
      +     *
      +     * @return bool
      +     */
      +    public function addProcessor(int $index, Processor  ...$processors): bool
      +    {
      +        ArrayHelper::insert($this->processors, $index, ...$processors);
      +
      +        return true;
      +    }
      +}
      +

      addFirstProcessor方法把process对象赋值给$this->processors

      +

      handle方法遍历processors对象,循环执行handle方法。

      +

      Swoft的核心逻辑都是靠上面定义的6个Processor模块实现的,接下来一个一个分析。

      +]]>
      +
      + + BCMath 与 科学计数 + https://liudon.com/posts/bcmath-and-exponential-notation/ + Fri, 16 Aug 2019 19:34:34 +0800 + https://liudon.com/posts/bcmath-and-exponential-notation/ + <p>代码如下</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>&lt;?php +</span></span><span style="display:flex;"><span>echo 9.99997600 + 2.4E-5; +</span></span><span style="display:flex;"><span>echo &#34;\n===\n&#34;; +</span></span><span style="display:flex;"><span>echo bcadd(9.99997600, 2.4E-5, 8); +</span></span></code></pre></div><p>结果为</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>10 +</span></span><span style="display:flex;"><span>=== +</span></span><span style="display:flex;"><span>9.99997600 +</span></span></code></pre></div><p>问了朋友,查了各种资料,终于在PHP手册里发现了这段话。</p> +<blockquote> +<p>Caution +Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).</p> + 代码如下

      +
      <?php
      +echo 9.99997600 + 2.4E-5;
      +echo "\n===\n";
      +echo bcadd(9.99997600, 2.4E-5, 8);
      +

      结果为

      +
      10
      +===
      +9.99997600
      +

      问了朋友,查了各种资料,终于在PHP手册里发现了这段话。

      +
      +

      Caution +Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).

      +
      +

      PHP的BCMath方法不支持科学计数

      +

      解决方法:

      +
      echo bcadd(9.99997600, number_format(2.4E-5, 8, '.', ''), 8);
      +

      PHP里浮点数相关的运算一定要使用BCMath函数!

      +]]>
      +
      + + Flink Could Not Resolve Resourcemanager Address + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + Thu, 28 Mar 2019 13:00:50 +0800 + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + <p>什么是Flink。</p> +<blockquote> +<p>Apache Flink® - Stateful Computations over Data Streams</p> +</blockquote> +<p>Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。</p> + 什么是Flink。

      +
      +

      Apache Flink® - Stateful Computations over Data Streams

      +
      +

      Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。

      +

      这里使用单机模式

      +

      问题表现

      +

      启动Flink

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ./bin/start-cluster.sh 
      +Starting cluster.
      +Starting standalonesession daemon on host VM_80_180_centos.
      +Starting taskexecutor daemon on host VM_80_180_centos.
      +

      查看进程

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# jps
      +10442 StandaloneSessionClusterEntrypoint
      +11067 Jps
      +10909 TaskManagerRunner
      +[root@VM_80_180_centos /usr/local/flink-1.7.2]# 
      +

      查看日志,发现"Could not resolve ResourceManager address"的错误。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# tail -f log/flink-root-taskexecutor-*.log
      +
      +2019-03-27 19:43:23,804 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +2019-03-27 19:43:43,843 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +

      访问Flink的web页面,发现task数全为0.

      +

      + +flink no task + + +

      +

      问题原因:

      +

      + +taskmanager.host + + +

      +

      Flink的taskmanager.host默认为空,会使用hostname。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ping VM_80_180_centos
      +PING VM_80_180_centos (100.125.80.180) 56(84) bytes of data.
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=1 ttl=64 time=0.022 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=2 ttl=64 time=0.038 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=3 ttl=64 time=0.038 ms
      +

      Flink的jobmanager.host默认为localhost。

      +

      这里jobmanager和taskmanager绑定的ip不一样,导致出错。

      +

      解决办法:

      +
      vim conf/flink-conf.yaml
      +
      +添加下面这行配置
      +taskmanager.host: localhost
      +

      保存退出,然后重新启动Flink,这个时候在web端就可以看到有可用task了。

      +

      + +flink web + + +

      +]]>
      +
      + + 解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错 + https://liudon.com/posts/there-are-no-packages-available-for-installation/ + Fri, 11 Jan 2019 17:13:14 +0800 + https://liudon.com/posts/there-are-no-packages-available-for-installation/ + <p>今天安装hugofy的包时,一直遇到&quot;There Are No Packages Available for Installation&quot;的错误。 +按网上的教程,配置host,配置代理都不起作用。</p> + 今天安装hugofy的包时,一直遇到"There Are No Packages Available for Installation"的错误。 +按网上的教程,配置host,配置代理都不起作用。

      +

      本机确定是可以访问https://packagecontrol.io/channel_v3.json这个地址的。

      +

      然后按教程把这文件放到本地,配置channel指向本地这个文件,然后提示json解析失败。 +然后检查这个文件,发现文件好像不全。然后换到其他机器curl这个地址,发现下载下来的文件确实不全,不是合法的json内容。

      +

      + +下载文件内容截图 + + +

      +

      又搜索一番后,找到一个case。

      +

      Package Control: There are no packages available for installation/Server Error

      +

      原来是官方的文件下载出问题了,可以先按上面链接里的方法修改,验证可行。

      +
      Meanwhile, you can add
      +"channels": [ "https://erhan.in/channel_v3.json" ],
      +to Preferences > Package Settings > Package Control > Settings - User file.
      +
      +This is the latest snapshot of the original JSON file from web.archive.org.
      +
      ]]>
      +
      + + 关于本站 + https://liudon.com/about/ + Thu, 10 Jan 2019 14:11:09 +0800 + https://liudon.com/about/ + <p>大家好,欢迎访问我的博客。</p> +<h4 id="建站缘由">建站缘由</h4> +<p>大学期间,被人忽悠报了一个计算机培训班,误打误撞的进入了互联网行业。</p> +<p>11年在网友的帮助下,用wordPress搭建了<a href="https://www.liudon.org/1.html">博客</a>,随即开始了我的博主生涯。</p> + 大家好,欢迎访问我的博客。

      +

      建站缘由

      +

      大学期间,被人忽悠报了一个计算机培训班,误打误撞的进入了互联网行业。

      +

      11年在网友的帮助下,用wordPress搭建了博客,随即开始了我的博主生涯。

      +

      最开始的域名是liudon.org,取自本人名字的拼音,14年的时候经历了一次域名被盗,好在最后找回了。

      +

      中间又申请了liudon.xyz域名,一直垂涎于liudon.com,终于在22年被我拿到手了,十一年的等待

      +

      本站使用hugo生成,存储在Github上,通过Cloudflare PagesIPFS部署访问。

      +

      关于本人

      +

      喜欢做梦、已婚的80后男生。

      +

      如果您有意见或建议,欢迎您通过i@liudon.org联系我。

      +

      朋友们

      + +
      +
      + +
      +
      +
      Dvel’s Blog
      +
      Less is More.
      +
      +
      +
      + +
      +
      + +
      +
      +
      林木木木木木
      +
      木木木木木
      +
      +
      +
      + +
      +
      + +
      +
      +
      涛叔
      +
      涛叔
      +
      +
      +
      + +
      +
      + +
      +
      +
      老张博客
      +
      老张博客
      +
      +
      +
      +]]>
      +
      + + 2019,新开始 + https://liudon.com/posts/the-first-post/ + Wed, 09 Jan 2019 15:17:04 +0800 + https://liudon.com/posts/the-first-post/ + <p>从2011年开始写博客,博客程序从WordPress换成Typecho。 +早就有想法换成静态博客,一直没时间搞。</p> +<p>2019年了,新年新气象,用hugo + github pages搞了个新博客。</p> + 从2011年开始写博客,博客程序从WordPress换成Typecho。 +早就有想法换成静态博客,一直没时间搞。

      +

      2019年了,新年新气象,用hugo + github pages搞了个新博客。

      +

      具体部署过程参考文章: +利用Travis CI和Hugo將Blog自動部署到Github Pages

      +

      这篇文章就是通过这种方式来更新的,感觉很是神奇。 +再也不用关注服务器性能这些东西了,只需要专心写字就好了。

      +

      接下来只需要搞定自定义域名了,域名还在认证中,无法做解析。

      +

      自定义域名也搞定了,以后就可以正式切到新博客了。

      +

      老博客只做备份了,不再更新了。

      +]]>
      +
      +
      +
      diff --git a/page/1/index.html b/page/1/index.html new file mode 100644 index 000000000..123ae3068 --- /dev/null +++ b/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/ + \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 000000000..612fd999f --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,69 @@ +流动 +

      利用Github Actions定时抓取微博

      背景 在微博上关注了一些用户,比如tk教主,月风。 +但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。 +实现方案 整体思路:利用Github Actions的Scheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。 +...

      2023-10-07 · 2 min · 823 words · Liudon

      北大口腔牙周刮治记录

      病情 上次洗完牙后,还是不时有出血的情况。 +前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。 +于是,又跑到医院来看牙了。 +医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。 +...

      2023-09-17 · 2 min · 716 words · Liudon

      故乡回忆之旅

      赶在8月底,趁着娃暑假的尾声,回了趟老家。 +老家有条俗语,“永福庄的街,三里长”。 +这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。 +小时候,整天在这条街上跑来跑去。 +...

      2023-09-09 · 1 min · 292 words · Liudon

      解决Golang使用go get安装包后找不到可执行文件的问题

      背景 编译流水线代码 +go get google.golang.org/protobuf/cmd/protoc-gen-go@latest protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto ... go build -o xxx 在go升级到1.20.1版本后,执行报错。 +protoc-gen-go: program not found or is not executable 解决 Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead. +In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled. +...

      2023-08-17 · 1 min · 195 words · Liudon

      修正Hugo的JSON Feed格式

      问题背景 前几天在Planet里follow自己的web3博客,遇到下面的错误。 +经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。 +因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。 +[ { "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n", "permalink": "https://dvel.me/posts/chd-quiz-answer/", "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!", "title": "CHD 油猴脚本:每日签到自动答题" }, ... ] 下面是一个JSON Feed的示例,详细规范见jsonfeed.org。 +...

      2023-03-25 · 3 min · 1451 words · Liudon

      我的学车之路

      之前在2022年终总结提到过,我在练车考驾照。 +就在昨天,终于拿证了。👏👏👏 +咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶 +2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 2022年10月12日,科目一考试通过。 2022年10月22日,科目二模拟驾驶。 2022年11月13日,科目二第一次上车练习。 2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 2023年2月4日,年后驾校恢复培训,继续科目二练车。 2023年2月13日,科目二考试通过。 2023年3月11日,科目三上路练习。 2023年3月23日,上午科目三考试通过,下午科目四考试通过。 考试的时候,早上遇到临时交通管制,一直到9点40才开考。 考完回来,班车上的人说又管制不能考了。 班车拉回驾校,剩下的人中午加班考试。 终于不用再5点半起床赶班车了。🥱 +...

      2023-03-24 · 1 min · 375 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon

      新冠疫情后的第一个春节

      下面的内容是由chatGPT润色生成的。 +AI太强大了 😂 +当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。 +但我从未想过,等我长大后,我也会成为其中的一员。 +...

      2023-02-16 · 1 min · 242 words · Liudon

      第一次清理键盘

      19年生日的时候,媳妇送了一款flico的机械键盘。 +这次搬家后,想着年前清理下键盘,实在是太脏了。 +周五下班,带上键盘回家。 +...

      2023-01-16 · 1 min · 141 words · Liudon

      2022年终总结

      2022年已经过去1周多了,记录一下我的2022年。 +疫情 2022年,是新冠疫情的第三年,也是切身感受到的一年。 +3月22日晚,8点半和同事刚上13号线地铁。 +...

      2023-01-12 · 2 min · 646 words · Liudon

      去掉Cloudflare烦人的email-decode.min.js请求

      通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。 +...

      2022-08-26 · 1 min · 180 words · Liudon
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高. +问题表现 7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。 +偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。 +...

      2022-08-20 · 2 min · 886 words · Liudon

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      向日葵

      向日葵

      奥林匹克公园向日葵之旅

      媳妇有事回老家了,这两天自己带娃。 +小区群里有人说奥林匹克公园的向日葵开了,适合拍照。 +正好周六多云,没有太阳,出门遛娃。 +带上我好久不用的相机,省得发霉了。 +...

      2022-07-21 · 1 min · 179 words · Liudon

      记第二次洗牙

      最近刷牙的时候,牙龈总是出血。 +距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。 +上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。 +...

      2022-06-21 · 1 min · 283 words · Liudon

      记录2022年海淀幼升小

      18年的热点新闻,纳税千万孩子无法在北京上学。 +一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。 +...

      2022-05-25 · 2 min · 715 words · Liudon

      Golang解析json的一个问题

      业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题: +请求接口,按返回包字段判断请求成功与否。 伪代码如下: +package main import ( "encoding/json" "fmt" ) type Response struct { Code int `json:"code"` Msg string `json:"msg"` } func main() { // 场景1,返回包符合接口要求 str := `{"code":100,"msg":"failed"}` var res Response json.Unmarshal([]byte(str), &res) fmt.Printf("res=%+v\n", res) // 解析正确,符合预期 // res={Code:100 Msg:failed} // 场景2,返回包不符合接口要求,缺少相关字段 str = `{"retCode":100,"retMsg":"failed"}` var res1 Response json.Unmarshal([]byte(str), &res1) fmt.Printf("res=%+v\n", res1) // 解析错误,不符合预期 // res={Code:0 Msg:} } 这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。 +...

      2022-05-20 · 1 min · 424 words · Liudon

      疫情下的生活

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。 +昨天看新闻,基本没有社会面新增了,感觉要解封了。 +没想到今天直接被打脸,封控升级了。 +...

      2022-05-20 · 1 min · 189 words · Liudon

      整理下博客的一些调整

      新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。 +更新主题版本,展示文章tag标签 通过对比主题作者的网站,发现使用的不是最新代码。 +...

      2022-05-13 · 1 min · 331 words · Liudon
      \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 000000000..a49c060bd --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,68 @@ +流动 +

      疫情下的五一假期

      五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。 +当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。 +说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。 +...

      2022-05-05 · 1 min · 431 words · Liudon

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon

      二刷百望山

      又是周末,娃约了小伙伴一起爬山。 +百望山,二刷走起。 +约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。 +出门晚了,还打不到车,快10点才到。 +小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。 +...

      2022-04-17 · 1 min · 209 words · Liudon

      带娃游颐和园

      上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。 +周六7点准时起床,得早点去省得人多排队。 +8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。 +...

      2022-04-11 · 1 min · 258 words · Liudon

      博客架构说明

      在拿到liudon.com域名前,手中已有两个域名: +liudon.org liudon.xyz liudon.org已经不再更新,仅作归档使用。 liudon.xyz当时是静态博客流行,尝鲜使用。 +...

      2022-04-10 · 1 min · 433 words · Liudon

      难得的清明假期

      前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。 +趁着这次难得的假期,外出放松一下。 +爬百望山。 +娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。 +...

      2022-04-06 · 1 min · 343 words · Liudon

      十一年的等待,终于拿到了liudon.com域名

      在关于部分,有写域名的来历。 +当时liudon.com已经被注册,所以只好注册了liudon.org。 +2011年注册的liudon.org,最早用wordpress搭建了博客。 +...

      2022-04-01 · 2 min · 588 words · Liudon

      被隔离的一周

      从没有想过疫情会离自己这么近,记录一下。 +周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。 +周二早上全员核酸阴性,继续到公司上班。 +...

      2022-04-01 · 1 min · 328 words · Liudon

      mysql中字符串和整型自动转换的问题

      表结构如下 +desc info; +-------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------+------+-----+---------+----------------+ | id | int(8) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | +-------+-----------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) 执行sql. +insert into info values ('', 'xxx'); insert into info values ('', 'yyy'); 查询记录. +select * from info; +----+------+ | id | name | +----+------+ | 1 | xxx | | 2 | yyy | +----+------+ 2 rows in set (0.00 sec) 执行下面sql. +...

      2020-12-14 · 1 min · 303 words · Liudon

      一次惊心动魄的Mysql更新操作

      问题描述 # 表结构 MySQL > desc user_packages; +----------------+---------------------+------+-----+---------------------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+---------------------+------+-----+---------------------+----------------+ | up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | start_date | date | NO | | NULL | | | end_date | date | NO | | NULL | | | up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | | up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +----------------+---------------------+------+-----+---------------------+----------------+ 5 rows in set (0.00 sec) MySQL > select * from user_packages limit 5; +-------+------------+------------+ | up_id | start_date | end_date | +-------+------------+------------+ | 185 | 2018-04-01 | 2018-06-30 | | 186 | 2018-04-01 | 2018-06-30 | | 187 | 2018-04-01 | 2018-06-30 | | 188 | 2018-04-01 | 2018-06-30 | | 189 | 2018-04-01 | 2018-06-30 | +-------+------------+------------+ 5 rows in set (0.00 sec) 操作过程 需要更新某条记录的end_date字段,执行sql如下: +...

      2020-05-19 · 2 min · 776 words · Liudon

      如何在北京公积金网站上修改婚姻状况

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告 +时间:2020年01月08日 +来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html +...

      2020-01-17 · 1 min · 438 words · Liudon

      PHP7.2编译安装后没有php.ini文件的问题

      下载PHP7.2源码,编译安装。 +[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies [root@VM_73_135_centos ~/swoole-src-4.4.12]# 安装Swoole。 +phpize && \ ./configure && \ make && make install 安装完,准备修改php.ini文件,结果没找到。 +...

      2019-11-26 · 1 min · 241 words · Liudon

      检测网站支持的SSL/TLS协议版本

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。 +为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。 +...

      2019-11-14 · 1 min · 205 words · Liudon

      记一次难忘的手术经历

      俗话说的好,十人九痔。这九个人里就有我一个。 😂 +去年因为痔疮去过一趟医院,医生当时建议手术。 +后来用了点药,没啥症状了,就不放在心上了。 +结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。 +...

      2019-10-28 · 2 min · 646 words · Liudon

      十一假期经历

      今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。 +只好请了2天假,提前回家了,给自己也放个假休息一下。 +回家的几个经历: +家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。 +...

      2019-10-08 · 1 min · 306 words · Liudon

      Swoft 框架运行分析(五) —— ConsoleProcessor模块分析

      这里以Swoft启动http server为例。 +php bin/swoft http:start +执行上述命令,启动http server。 +在前面第一篇文章的时候,提到了如何启动http服务。 +今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。 +...

      2019-09-26 · 10 min · 4524 words · Liudon

      Swoft 框架运行分析(四) —— EventProcessor模块分析

      今天我们来看一下EventProcessor的实现。 +/** * Handle event register * @return bool */ public function handle(): bool { if (!$this->application->beforeEvent()) { CLog::warning('Stop event processor by beforeEvent return false'); return false; } /** @var EventManager $eventManager */ $eventManager = bean('eventManager'); [$count1, $count2] = ListenerRegister::register($eventManager); CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2); // Trigger a app init event Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); return $this->application->afterEvent(); } 获取eventManager的Bean实例,对应为Swoft\Event\Manager\EventManager类。 +...

      2019-09-26 · 4 min · 1896 words · Liudon

      一个git submodule update引发的问题

      背景 1月份的时候,用hugo搭了这套博客系统。 +本机写md文件,更新到github,然后通过travis-ci自动发布。 +jane主题是通过git submodule引入的,.gitmodules文件内容。 +...

      2019-09-06 · 4 min · 1748 words · Liudon

      一个Curl的耗时长的问题

      发现某个接口请求很慢,但是后端确认接口是很快的。 +在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。 +业务里用到了Requests这个库,一开始以为是这个库导致的问题。 +...

      2019-09-04 · 2 min · 925 words · Liudon

      Swoft 框架运行分析(三) —— BeanProcessor模块分析

      今天讲一下BeanProcessor模块,先看一下handle方法实现。 +/** * Handle bean * * @return bool * @throws ReflectionException * @throws AnnotationException */ public function handle(): bool { if (!$this->application->beforeBean()) { return false; } $handler = new BeanHandler(); $definitions = $this->getDefinitions(); $parsers = AnnotationRegister::getParsers(); $annotations = AnnotationRegister::getAnnotations(); BeanFactory::addDefinitions($definitions); BeanFactory::addAnnotations($annotations); BeanFactory::addParsers($parsers); BeanFactory::setHandler($handler); BeanFactory::init(); /* @var Config $config*/ $config = BeanFactory::getBean('config'); CLog::info('config path=%s', $config->getPath()); CLog::info('config env=%s', $config->getEnv()); $stats = BeanFactory::getStats(); CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats)); return $this->application->afterBean(); } 先通过getDefinitions方法获取所有的Bean定义。 +...

      2019-09-02 · 9 min · 4044 words · Liudon
      \ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 000000000..19064632a --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,20 @@ +流动 +

      Swoft 框架运行分析(二) —— AnnotationProcessor模块分析

      上一篇介绍了,SwoftApplication里定义了6个Processor对象。 +protected function processors(): array { return [ new EnvProcessor($this), new ConfigProcessor($this), new AnnotationProcessor($this), new BeanProcessor($this), new EventProcessor($this), new ConsoleProcessor($this), ]; } 所有的Processor实现都在framework\src\Processor目录下。 +...

      2019-08-29 · 4 min · 1656 words · Liudon

      Swoft 框架运行分析(一)

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。 +以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。 +刚开始看的时候,感觉自己像个原始人,完全看不懂。 +...

      2019-08-29 · 2 min · 966 words · Liudon

      BCMath 与 科学计数

      代码如下 +<?php echo 9.99997600 + 2.4E-5; echo "\n===\n"; echo bcadd(9.99997600, 2.4E-5, 8); 结果为 +10 === 9.99997600 问了朋友,查了各种资料,终于在PHP手册里发现了这段话。 +Caution Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point). +...

      2019-08-16 · 1 min · 168 words · Liudon

      Flink Could Not Resolve Resourcemanager Address

      什么是Flink。 +Apache Flink® - Stateful Computations over Data Streams +Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。 +...

      2019-03-28 · 2 min · 517 words · Liudon

      解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错

      今天安装hugofy的包时,一直遇到"There Are No Packages Available for Installation"的错误。 按网上的教程,配置host,配置代理都不起作用。 +...

      2019-01-11 · 1 min · 336 words · Liudon

      2019,新开始

      从2011年开始写博客,博客程序从WordPress换成Typecho。 早就有想法换成静态博客,一直没时间搞。 +2019年了,新年新气象,用hugo + github pages搞了个新博客。 +...

      2019-01-09 · 1 min · 250 words · Liudon
      \ No newline at end of file diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/QWh2AALxlx08aXnxzCB201041200h2nD0E018.mp4" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/QWh2AALxlx08aXnxzCB201041200h2nD0E018.mp4" new file mode 100644 index 000000000..1dbfb953e Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/QWh2AALxlx08aXnxzCB201041200h2nD0E018.mp4" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150.jpg" new file mode 100644 index 000000000..0c63cf11b Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1013573127101826915.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1013573127101826915.jpg" new file mode 100644 index 000000000..b799c075d Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1013573127101826915.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1184931133622624991.webp" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1184931133622624991.webp" new file mode 100644 index 000000000..6a1b3a8f8 Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG150_hu1184931133622624991.webp" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151.jpg" new file mode 100644 index 000000000..3aed9a157 Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu4695072437237824166.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu4695072437237824166.jpg" new file mode 100644 index 000000000..206a3f151 Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu4695072437237824166.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu5204297538520097810.webp" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu5204297538520097810.webp" new file mode 100644 index 000000000..ee864251b Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG151_hu5204297538520097810.webp" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152.jpg" new file mode 100644 index 000000000..47f42dd6e Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu11895712938102014864.webp" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu11895712938102014864.webp" new file mode 100644 index 000000000..78ac16aac Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu11895712938102014864.webp" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu18213560282436426467.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu18213560282436426467.jpg" new file mode 100644 index 000000000..453fda89b Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG152_hu18213560282436426467.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153.jpg" new file mode 100644 index 000000000..5ec172f8f Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu4813959692554313898.jpg" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu4813959692554313898.jpg" new file mode 100644 index 000000000..b11a4a742 Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu4813959692554313898.jpg" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu998159380452732360.webp" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu998159380452732360.webp" new file mode 100644 index 000000000..79e30a6c3 Binary files /dev/null and "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/WechatIMG153_hu998159380452732360.webp" differ diff --git "a/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/index.html" "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/index.html" new file mode 100644 index 000000000..119716731 --- /dev/null +++ "b/posts/2023\345\271\26412\346\234\210\345\214\227\344\272\254\346\232\264\351\233\252\350\256\260\345\275\225/index.html" @@ -0,0 +1,25 @@ +2023年12月北京暴雪记录 | 流动 +

      2023年12月北京暴雪记录

      记录暴雪下普通打工人的生活。

      12月14日 周四

      北京的雪已经连着下了两天了。

      12月11日,也是因为下雪,晚上打车打到10点半才叫到车。

      所以这次下雪后,晚上就早走了。

      7:15坐上公司摆渡车,7:40左右到西二旗。

      下车前刷微博看到有说昌平线故障,还没细看就到站下车了,还没想到事情的严重性。

      路口已经有交警在指挥交通,看见两辆消防车在等红灯,事后想可能是去救援的。

      走到进站口,就听到有人喊,“昌平线故障不停车,请更换其他交通工具”。

      我坐13号线,继续往前走,就看到了下面的情景。

      西二旗进站口

      栏杆里全是人,后面的人还在不断的往里进。

      看了下门口的人不动,人太多,不敢进去,先看看情况。

      过了一会,有人喊13号线也停了,扭头赶紧走。

      打车等候人数

      第一反应打车,看了下,排队900人,直接放弃,换公交。

      最近的公交站在西二旗大街,走吧。

      到公交站一看,人更多,本来路就窄,全是人,走都没法走。

      公交也没戏了,继续走吧,走过这段到前面看看再。

      事后看说有的车被挤爆了,吓的别的车都不敢开门了。

      走的有点冷,闻见一股香味,一看手机快8点半了,找个地吃点东西先。

      吉祥馄饨

      一碗馄饨下肚,暖和多了,继续上路。

      路上

      走的小腿有点酸了,终于到公交站了。

      等了一小会,总算上车了。

      车也不敢开的快,10迈左右。

      晚上10点,终于到家了。

      路上花费2小时45分钟

      12月15日 周五

      早上坐公交到地铁站。

      霍营地铁站盛况,人挤满了整个通道。

      立马出站,换乘公交。

      路上花费2小时

      💬评论
      \ No newline at end of file diff --git a/posts/a-issues-of-git-submodule-update/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png b/posts/a-issues-of-git-submodule-update/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png new file mode 100644 index 000000000..c851301c1 Binary files /dev/null and b/posts/a-issues-of-git-submodule-update/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png differ diff --git a/posts/a-issues-of-git-submodule-update/64412419-e4f46c00-d0c1-11e9-8c2d-6fa1581529f3.png b/posts/a-issues-of-git-submodule-update/64412419-e4f46c00-d0c1-11e9-8c2d-6fa1581529f3.png new file mode 100644 index 000000000..fab50bde2 Binary files /dev/null and b/posts/a-issues-of-git-submodule-update/64412419-e4f46c00-d0c1-11e9-8c2d-6fa1581529f3.png differ diff --git a/posts/a-issues-of-git-submodule-update/index.html b/posts/a-issues-of-git-submodule-update/index.html new file mode 100644 index 000000000..075e804d6 --- /dev/null +++ b/posts/a-issues-of-git-submodule-update/index.html @@ -0,0 +1,181 @@ +一个git submodule update引发的问题 | 流动 +

      一个git submodule update引发的问题

      背景

      1月份的时候,用hugo搭了这套博客系统。

      本机写md文件,更新到github,然后通过travis-ci自动发布。

      jane主题是通过git submodule引入的,.gitmodules文件内容。

      [submodule "themes/jane"]
      +	path = themes/jane
      +	url = https://github.com/xianmin/hugo-theme-jane.git
      +

      问题

      最近几天更新完文章后,发现首页显示出了问题。

      一开始以为是主题有问题,具体描述见首页文章不显示了

      issue里: +shaform提到使用的并不是最新的版本。 +RocFang提到是git submodule使用的问题。

      但是travis-ci每次都是通过git submodule update --init --recursive更新子仓库代码的,为什么会不是最新的代码呢。

      问题重现

      接下来,我们用一个新的仓库,来模拟重现一下。

      1. 克隆仓库。

        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +[root@VM_81_18_centos test]# 
        +
      2. 添加文件。

         [root@VM_81_18_centos xx]# cd test/
        + [root@VM_81_18_centos test]# echo "# test" >> README.md
        + [root@VM_81_18_centos test]# git add README.md
        + [root@VM_81_18_centos test]# 
        +
      3. 引用子仓库。

         [root@VM_81_18_centos test]# git submodule add git@github.com:xianmin/hugo-theme-jane.git theme/jane
        + Cloning into 'theme/jane'...
        + remote: Enumerating objects: 216, done.
        + remote: Counting objects: 100% (216/216), done.
        + remote: Compressing objects: 100% (128/128), done.
        + remote: Total 6165 (delta 102), reused 159 (delta 65), pack-reused 5949
        + Receiving objects: 100% (6165/6165), 3.05 MiB | 1.70 MiB/s, done.
        + Resolving deltas: 100% (3443/3443), done.
        +
      4. 查看文件列表。

         [root@VM_81_18_centos test]# ll
        + total 8
        + -rw-r--r-- 1 root root    5 Sep  6 16:05 README.md
        + drwxr-xr-x 7 root root 4096 Sep  6 16:08 typecho
        + [root@VM_81_18_centos test]# 
        +
      5. 查看状态。

        [root@VM_81_18_centos test]# git status
        +# On branch master
        +#
        +# Initial commit
        +#
        +# Changes to be committed:
        +#   (use "git rm --cached <file>..." to unstage)
        +#
        +#	new file:   .gitmodules
        +#	new file:   README.md
        +#	new file:   typecho
        +#
        +[root@VM_81_18_centos test]# 
        +
      6. 查看修改。

        [root@VM_81_18_centos test]# git diff --cached
        +diff --git a/.gitmodules b/.gitmodules
        +new file mode 100644
        +index 0000000..b1ddf70
        +--- /dev/null
        ++++ b/.gitmodules
        +@@ -0,0 +1,3 @@
        ++[submodule "typecho"]
        ++       path = typecho
        ++       url = https://github.com/Liudon/typecho
        +diff --git a/README.md b/README.md
        +new file mode 100644
        +index 0000000..9daeafb
        +--- /dev/null
        ++++ b/README.md
        +@@ -0,0 +1 @@
        ++test
        +diff --git a/typecho b/typecho
        +new file mode 160000
        +index 0000000..b0c4cc7
        +--- /dev/null
        ++++ b/typecho
        +@@ -0,0 +1 @@
        ++Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +[root@VM_81_18_centos test]# 
        +

        注意最后一行Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1

        这个commitId是子仓库最新提交的记录id,对应的修改记录

      7. 提交修改。

        [root@VM_81_18_centos test]# git push -u origin master
        +Counting objects: 4, done.
        +Compressing objects: 100% (3/3), done.
        +Writing objects: 100% (4/4), 362 bytes | 0 bytes/s, done.
        +Total 4 (delta 0), reused 0 (delta 0)
        +To git@github.com:Liudon/test.git
        +* [new branch]      master -> master
        +Branch master set up to track remote branch master from origin.
        +[root@VM_81_18_centos test]# 
        +

        提交后,在github上子仓库后面会多显示一个@xxxxx,这里就是引用的commitId,对应到前面git diff最后一行。

        点击查看提交记录

        本次提交的commitId5b11d515db8ad8d299ef1691f115590e0015c3b7,子仓库typecho单独记录了引入时的commitId,为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,对应的提交记录

      8. 接下来克隆子仓库,进行更新提交。

        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/typecho.git
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 1, done.
        +remote: Counting objects: 100% (1/1), done.
        +remote: Total 7179 (delta 0), reused 0 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7179/7179), 7.26 MiB | 2.02 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +[root@VM_81_18_centos xx]# 
        +[root@VM_81_18_centos xx]# cd typecho/
        +[root@VM_81_18_centos typecho]# git log -n 1
        +commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +Merge: c904005 8fd7492
        +Author: 祁宁 <magike.net@gmail.com>
        +Date:   Tue Nov 18 13:59:52 2014 +0800
        +
        +    Merge branch 'master' of https://github.com/typecho/typecho
        +[root@VM_81_18_centos typecho]#
        +

        通过git log,确认最新的提交commitId为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面的引入的一致。

        [root@VM_81_18_centos typecho]# echo "xxx" > test
        +[root@VM_81_18_centos typecho]# 
        +[root@VM_81_18_centos typecho]# git add test
        +[root@VM_81_18_centos typecho]# git commit -m 'test'
        +[master 5dcc8f4] test
        +1 file changed, 1 insertion(+)
        +create mode 100644 test
        +[root@VM_81_18_centos typecho]# git push
        +Counting objects: 4, done.
        +Compressing objects: 100% (2/2), done.
        +Writing objects: 100% (3/3), 252 bytes | 0 bytes/s, done.
        +Total 3 (delta 1), reused 0 (delta 0)
        +remote: Resolving deltas: 100% (1/1), completed with 1 local object.
        +To git@github.com:Liudon/typecho.git
        +b0c4cc7..5dcc8f4  master -> master
        +[root@VM_81_18_centos typecho]# 
        +

        修改文件提交。

        [root@VM_81_18_centos typecho]# git log -n 1
        +commit 5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2
        +Author: liudon <i.mu@qq.com>
        +Date:   Fri Sep 6 16:26:47 2019 +0800
        +
        +    test
        +[root@VM_81_18_centos typecho]#
        +

        最新提交的commitId5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2

      9. 重新克隆test库。

        [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
        +Receiving objects: 100% (4/4), done.
        +[root@VM_81_18_centos yy]# cd test/
        +[root@VM_81_18_centos test]# ll
        +total 8
        +-rw-r--r-- 1 root root    5 Sep  6 16:31 README.md
        +drwxr-xr-x 2 root root 4096 Sep  6 16:31 typecho
        +[root@VM_81_18_centos test]# ll typecho/
        +total 0
        +[root@VM_81_18_centos test]# 
        +

        这里可以看到typecho目录下是没有文件的。

        [root@VM_81_18_centos test]# git submodule update --init --recursive
        +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.26 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +Submodule path 'typecho': checked out 'b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1'
        +[root@VM_81_18_centos test]#
        +

        更新子仓库代码,这里可以看到最终checkout的版本为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面提交时的版本一致。

      问题分析

      git submodule add的时候,会记录当时引入时子仓库的版本id。

      git submodule update --init --recursive,会检出引入时的仓库版本,这就是为啥代码没有更新了。

      问题解决

      [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
      +Cloning into 'test'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
      +Receiving objects: 100% (4/4), done.
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# cd test/
      +[root@VM_81_18_centos test]# ll
      +total 8
      +-rw-r--r-- 1 root root    5 Sep  6 16:37 README.md
      +drwxr-xr-x 2 root root 4096 Sep  6 16:37 typecho
      +[root@VM_81_18_centos test]# ll typecho/
      +total 0
      +[root@VM_81_18_centos test]# 
      +[root@VM_81_18_centos test]# git submodule update --init --remote --recursive
      +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
      +Cloning into 'typecho'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
      +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.24 MiB/s, done.
      +Resolving deltas: 100% (4844/4844), done.
      +Submodule path 'typecho': checked out '5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2'
      +[root@VM_81_18_centos test]#
      +

      使用git submodule update --init --remote --recursive命令。

      💬评论
      \ No newline at end of file diff --git a/posts/an-unforgettable-surgical-experience/index.html b/posts/an-unforgettable-surgical-experience/index.html new file mode 100644 index 000000000..5ba0de818 --- /dev/null +++ b/posts/an-unforgettable-surgical-experience/index.html @@ -0,0 +1,6 @@ +记一次难忘的手术经历 | 流动 +

      记一次难忘的手术经历

      有痔青年的手术经历

      俗话说的好,十人九痔。这九个人里就有我一个。 😂

      去年因为痔疮去过一趟医院,医生当时建议手术。

      后来用了点药,没啥症状了,就不放在心上了。

      结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。

      第二天赶紧上医院检查,先上开塞露通便,通完舒服多了。

      医生检查完,让住院手术,这次狠了狠心,手术做掉,一了百了。

      1. 10/15住院
      2. 10/16手术
      3. 10/22出院

      上一次手术是在10年前了,全麻,什么都不知道。

      这次是局麻,上手术台后,医生往腰上打了一针,很疼。

      过了10分钟左右,肛门已经使不上劲了,开始手术。

      过程一直感觉有往里打气,东西插进去。

      医生说有用镇静,后来越来越困,感觉要晕过去,不过还是撑着没睡。

      出来后,整个下午下半身都是麻的,没啥精神,事后就记得媳妇坐在我对面。

      术后6个小时内要小便,不然就得插尿管。

      第二天开始喝流食,然后就是每天医生给换药。

      第三天开始大便,伤口那个痛啊。 😭😭😭

      后来学会在热水里泡着拉,感觉舒服了点。

      每天都要担心大便,担心不拉,或者担心拉的太多。

      出院后第二天,早上大便一盆血,以为是大出血,把媳妇叫回来直奔医院。

      到医院病房,主治医生不在,找了个其他医生给检查,说是有点出血。

      检查的时候塞了个肛门镜,这玩意实在是痛苦。

      然后说病房没工具,上门诊处理吧。然后我就拖着身子,走到门诊。

      主治医生说没啥大事,其实可以不处理,不过你也是来医院了,还是处理一下吧。

      处理的时候,又塞了个肛门镜,然后往屁眼上打了三针封闭,想死的心都有了。

      回家后,继续卧床休息,每天换药。

      朋友们,请一定要好好对你的🌻。

      💬评论
      \ No newline at end of file diff --git a/posts/bcmath-and-exponential-notation/index.html b/posts/bcmath-and-exponential-notation/index.html new file mode 100644 index 000000000..b86c9a582 --- /dev/null +++ b/posts/bcmath-and-exponential-notation/index.html @@ -0,0 +1,51 @@ +BCMath 与 科学计数 | 流动 +

      BCMath 与 科学计数

      代码如下

      <?php
      +echo 9.99997600 + 2.4E-5;
      +echo "\n===\n";
      +echo bcadd(9.99997600, 2.4E-5, 8);
      +

      结果为

      10
      +===
      +9.99997600
      +

      问了朋友,查了各种资料,终于在PHP手册里发现了这段话。

      Caution +Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).

      PHP的BCMath方法不支持科学计数

      解决方法:

      echo bcadd(9.99997600, number_format(2.4E-5, 8, '.', ''), 8);
      +

      PHP里浮点数相关的运算一定要使用BCMath函数!

      💬评论
      \ No newline at end of file diff --git a/posts/building-a-workout-page/20240922-170856.png b/posts/building-a-workout-page/20240922-170856.png new file mode 100644 index 000000000..4a2415ce8 Binary files /dev/null and b/posts/building-a-workout-page/20240922-170856.png differ diff --git a/posts/building-a-workout-page/20240922-170856_hu10697918706284234294.webp b/posts/building-a-workout-page/20240922-170856_hu10697918706284234294.webp new file mode 100644 index 000000000..3aea4896f Binary files /dev/null and b/posts/building-a-workout-page/20240922-170856_hu10697918706284234294.webp differ diff --git a/posts/building-a-workout-page/20240922-170856_hu9127555457854769002.png b/posts/building-a-workout-page/20240922-170856_hu9127555457854769002.png new file mode 100644 index 000000000..d19daaced Binary files /dev/null and b/posts/building-a-workout-page/20240922-170856_hu9127555457854769002.png differ diff --git a/posts/building-a-workout-page/index.html b/posts/building-a-workout-page/index.html new file mode 100644 index 000000000..ba673e45b --- /dev/null +++ b/posts/building-a-workout-page/index.html @@ -0,0 +1,15 @@ +搭建个人锻炼页面 | 流动 +

      搭建个人锻炼页面

      工作的缘故,平时基本一坐一天,缺少运动。

      时间久了,各种毛病也就出来了。

      搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。

      坚持了一段时间,也不了了之了。

      今年开始,决定骑车通勤,上下班路上运动一下。

      最近在别人博客里发现了运动记录,发现是通过running_page项目实现的。

      顺藤摸瓜,又发现了workouts_page项目,支持多种运动。

      于是看文档,部署起来,我的个人锻炼页面

      workout page

      整个流程:

      使用Apple Watch记录运动,导入到Strava应用里,在通过workouts_page工作流拉取数据构建页面。

      部署过程中,顺带发现个问题,提了个PR。

      这里还有个小插曲,没搞明白PR的流程,在未合入前又提交了其他代码,只好重新提了一个PR。😂

      💬评论
      \ No newline at end of file diff --git a/posts/chinese-national-day/SkRx5uFwQ8Cliyq.jpg b/posts/chinese-national-day/SkRx5uFwQ8Cliyq.jpg new file mode 100644 index 000000000..c0a44917a Binary files /dev/null and b/posts/chinese-national-day/SkRx5uFwQ8Cliyq.jpg differ diff --git a/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu10598085116215178875.webp b/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu10598085116215178875.webp new file mode 100644 index 000000000..579709928 Binary files /dev/null and b/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu10598085116215178875.webp differ diff --git a/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu11211928567423584928.jpg b/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu11211928567423584928.jpg new file mode 100644 index 000000000..a0c59fc80 Binary files /dev/null and b/posts/chinese-national-day/SkRx5uFwQ8Cliyq_hu11211928567423584928.jpg differ diff --git a/posts/chinese-national-day/index.html b/posts/chinese-national-day/index.html new file mode 100644 index 000000000..dd2293ea9 --- /dev/null +++ b/posts/chinese-national-day/index.html @@ -0,0 +1,24 @@ +十一假期经历 | 流动 +

      十一假期经历

      今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。

      只好请了2天假,提前回家了,给自己也放个假休息一下。


      回家的几个经历:

      1. 家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。

        64F68D7789A8D877A41E6D694ABE5444.png

      2. 村里今年也要通天然气了,各家各户都要拆煤炉,装壁挂炉,希望天然气供应不出问题。

      3. 全家一起去了趟家门口的园博园,希望以后可以走的更远一些。

        F69E19D28B34C163878F2A6E1A43E039.png

      4. 娃确实是长大了,有了自己的想法,有自己的脾气了。

        背景:晚上开着灯,要睡觉了。

        我:睡觉吧。

        娃:爸爸,你说开着灯能睡觉吗?

        我:不能吧…(不知道她为啥问这个问题)

        娃:那你开着灯让我睡觉,我怎么睡呀!

        我:原来你在这里等着我呢啊…


      接下来,就努力工作吧。

      💬评论
      \ No newline at end of file diff --git a/posts/chinese-national-day/wfUv5Kb1LGEdNHc.jpg b/posts/chinese-national-day/wfUv5Kb1LGEdNHc.jpg new file mode 100644 index 000000000..18122c491 Binary files /dev/null and b/posts/chinese-national-day/wfUv5Kb1LGEdNHc.jpg differ diff --git a/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu15815707449295317792.webp b/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu15815707449295317792.webp new file mode 100644 index 000000000..9dd36969c Binary files /dev/null and b/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu15815707449295317792.webp differ diff --git a/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu2876570433028549323.jpg b/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu2876570433028549323.jpg new file mode 100644 index 000000000..56f415d4c Binary files /dev/null and b/posts/chinese-national-day/wfUv5Kb1LGEdNHc_hu2876570433028549323.jpg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450456.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456.jpeg new file mode 100644 index 000000000..204990b9b Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu4699261132071604120.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu4699261132071604120.webp new file mode 100644 index 000000000..0c695dbb2 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu4699261132071604120.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu5926788081790166205.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu5926788081790166205.jpeg new file mode 100644 index 000000000..d7f07215e Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450456_hu5926788081790166205.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450468.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468.jpeg new file mode 100644 index 000000000..1a97221bd Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu12282089114328179317.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu12282089114328179317.jpeg new file mode 100644 index 000000000..e20cb7f3f Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu12282089114328179317.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu14483120261100559418.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu14483120261100559418.webp new file mode 100644 index 000000000..9f36ef766 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450468_hu14483120261100559418.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450480.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480.jpeg new file mode 100644 index 000000000..85fc282f6 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu22231389970031080.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu22231389970031080.webp new file mode 100644 index 000000000..ddc7c91ff Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu22231389970031080.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu8998630966370514229.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu8998630966370514229.jpeg new file mode 100644 index 000000000..e1b10f077 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450480_hu8998630966370514229.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450488.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488.jpeg new file mode 100644 index 000000000..a2d3c7789 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu12197024377809750737.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu12197024377809750737.webp new file mode 100644 index 000000000..cebedfac7 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu12197024377809750737.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu1230322316630236627.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu1230322316630236627.jpeg new file mode 100644 index 000000000..9ccfa052a Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450488_hu1230322316630236627.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450497.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497.jpeg new file mode 100644 index 000000000..2aab23b18 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu10633723481158909138.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu10633723481158909138.webp new file mode 100644 index 000000000..2b73dfd68 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu10633723481158909138.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu5465722458334428985.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu5465722458334428985.jpeg new file mode 100644 index 000000000..46ffa7788 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450497_hu5465722458334428985.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450505.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505.jpeg new file mode 100644 index 000000000..d38afe944 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu11173033394433915479.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu11173033394433915479.webp new file mode 100644 index 000000000..7ba60f7dc Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu11173033394433915479.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu1829993996032645083.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu1829993996032645083.jpeg new file mode 100644 index 000000000..fbe5b211a Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450505_hu1829993996032645083.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450514.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514.jpeg new file mode 100644 index 000000000..fd35cfa7f Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu14958352110478307340.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu14958352110478307340.webp new file mode 100644 index 000000000..b59a8f836 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu14958352110478307340.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu9420226909371823587.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu9420226909371823587.jpeg new file mode 100644 index 000000000..668a10bf2 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450514_hu9420226909371823587.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450523.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523.jpeg new file mode 100644 index 000000000..3d100cec5 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp new file mode 100644 index 000000000..5b933e3cd Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg new file mode 100644 index 000000000..34121bd42 Binary files /dev/null and b/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg differ diff --git a/posts/cleaning-the-keyboard-for-the-first-time/index.html b/posts/cleaning-the-keyboard-for-the-first-time/index.html new file mode 100644 index 000000000..6c590d736 --- /dev/null +++ b/posts/cleaning-the-keyboard-for-the-first-time/index.html @@ -0,0 +1,30 @@ +第一次清理键盘 | 流动 +

      第一次清理键盘

      19年生日的时候,媳妇送了一款flico的机械键盘。

      这次搬家后,想着年前清理下键盘,实在是太脏了。

      周五下班,带上键盘回家。

      键盘全貌,上面好多油。

      开拆。

      手还是太慢,上工具吧。

      全部拆完。

      内部特写。

      清理出来的灰屑、头发,这键盘见证了我的发迹线变化

      终于清理干净了。

      复原,又可以咔咔写代码了。

      💬评论
      \ No newline at end of file diff --git a/posts/curl-cost-time-long/index.html b/posts/curl-cost-time-long/index.html new file mode 100644 index 000000000..5493203fd --- /dev/null +++ b/posts/curl-cost-time-long/index.html @@ -0,0 +1,89 @@ +一个Curl的耗时长的问题 | 流动 +

      一个Curl的耗时长的问题

      发现某个接口请求很慢,但是后端确认接口是很快的。

      在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。

      业务里用到了Requests这个库,一开始以为是这个库导致的问题。

      Requests_Transport_cURL类里断点定位了下,确实很慢,curl_getinfo返回的信息如下。

      array (
      +  'url' => 'http://xxxxx',
      +  'content_type' => 'text/html',
      +  'http_code' => 200,
      +  'header_size' => 64,
      +  'request_size' => 305,
      +  'filetime' => -1,
      +  'ssl_verify_result' => 0,
      +  'redirect_count' => 0,
      +  'total_time' => 2.074094,
      +  'namelookup_time' => 2.5E-5,
      +  'connect_time' => 0.032107,
      +  'pretransfer_time' => 0.032109,
      +  'size_upload' => 186,
      +  'size_download' => 99,
      +  'speed_download' => 47,
      +  'speed_upload' => 89,
      +  'download_content_length' => 99,
      +  'upload_content_length' => 186,
      +  'starttransfer_time' => 2.032866,
      +  'redirect_time' => 0,
      +  'certinfo' =>
      +  array (
      +  ),
      +)
      +

      这里可以看到starttransfer_time时间很长。

      搜索了一番,发现网上一个case,cURL slow starttransfer_time

      里面提供了Expect: 100-continue这个header,又搜索了一番这个header资料。

      curl在发POST请求的时候,如果body大于1k:

      1. 先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
      2. 如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server; +如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。

      在机器上抓了个包,执行下面命令。

      注意,下面port后面的80改成实际的端口
      +
      +tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
      +

      拿到的包信息。

      09:17:19.421587 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306
      +E..f.m@.@...d}@.        A...XF.-.@...h....s.......
      +....T.0TPOST /cgi HTTP/1.1
      +User-Agent: php-requests/1.6
      +Accept: */*
      +Accept-Encoding: deflate, gzip
      +Referer: http://xxxxx:12345/cgi
      +Content-Length: 188
      +Expect: 100-continue
      +Content-Type: multipart/form-data; boundary=----------------------------ee2f4d848646
      +
      +
      +09:17:21.421786 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188
      +E....n@.@..Md}@.        A...XF.-.B...h....s./.....
      +....T.0[------------------------------ee2f4d848646
      +Content-Disposition: form-data; name="req"
      +
      +{"command":"zzz","appId":"yyyy"}
      +------------------------------ee2f4d848646--
      +
      +09:17:21.458628 IP xxxxx:12345 > xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117
      +E...X.@.5.Q2    A..d}@.F..X..h.-.B......3.....
      +T.2Q....HTTP/1.1 200 OK
      +Content-Type: text/html
      +Content-Length: 53
      +
      +{
      +    "data": [],
      +    "errno": 0,
      +    "error": "ok"
      +}
      +

      可以看到确实是先发了一个100-continue的请求,然后再发的实际POST请求。

      在机器上执行下面的shell命令。

      curl 'http://xxxxx:12345/cgi' -H"Expect: 100-continue" -v
      +

      返回如下,可以看到返回的header头里确实没有Expect这项。

      * About to connect() to xxxxx port 12345 (#0)
      +*   Trying xxxxx...
      +* Connected to xxxxx (xxxxx) port 12345 (#0)
      +> GET /cloud_cgi HTTP/1.1
      +> User-Agent: curl/7.29.0
      +> Host: xxxxx:12345
      +> Accept: */*
      +> Expect: 100-continue
      +> 
      +< HTTP/1.1 200 OK
      +< Content-Type: text/html
      +< Content-Length: 42
      +< 
      +* Connection #0 to host xxxxx left intact
      +{"errno":100,"error":"参数格式错误"}
      +

      解决方法:

      请求的时候,header里新增一项。

      Expect:
      +
      💬评论
      \ No newline at end of file diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729232638.png b/posts/deploy-blog-to-cloudflare-pages/20220729232638.png new file mode 100644 index 000000000..0d7edb4b9 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729232638.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu11689934850592818246.webp b/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu11689934850592818246.webp new file mode 100644 index 000000000..c2a3ef255 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu11689934850592818246.webp differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu8988839328733036755.png b/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu8988839328733036755.png new file mode 100644 index 000000000..625acef91 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729232638_hu8988839328733036755.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234310.png b/posts/deploy-blog-to-cloudflare-pages/20220729234310.png new file mode 100644 index 000000000..ffc7c09f0 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234310.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu13165477065719777794.png b/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu13165477065719777794.png new file mode 100644 index 000000000..879ad669f Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu13165477065719777794.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu8654492111814926388.webp b/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu8654492111814926388.webp new file mode 100644 index 000000000..99f170a8a Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234310_hu8654492111814926388.webp differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234321.png b/posts/deploy-blog-to-cloudflare-pages/20220729234321.png new file mode 100644 index 000000000..54f8781a4 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234321.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu14766569725942199582.webp b/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu14766569725942199582.webp new file mode 100644 index 000000000..cf5f3a4bc Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu14766569725942199582.webp differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu2249864527199419434.png b/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu2249864527199419434.png new file mode 100644 index 000000000..da4db53a0 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234321_hu2249864527199419434.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234651.png b/posts/deploy-blog-to-cloudflare-pages/20220729234651.png new file mode 100644 index 000000000..e30128abf Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234651.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu10659876291221712051.png b/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu10659876291221712051.png new file mode 100644 index 000000000..1160f9fac Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu10659876291221712051.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu4574317858186948276.webp b/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu4574317858186948276.webp new file mode 100644 index 000000000..240c4818a Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234651_hu4574317858186948276.webp differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234942.png b/posts/deploy-blog-to-cloudflare-pages/20220729234942.png new file mode 100644 index 000000000..e761cfc9a Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234942.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu8433537545901937906.png b/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu8433537545901937906.png new file mode 100644 index 000000000..5a9347de5 Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu8433537545901937906.png differ diff --git a/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu9149058069851839611.webp b/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu9149058069851839611.webp new file mode 100644 index 000000000..f35734a9e Binary files /dev/null and b/posts/deploy-blog-to-cloudflare-pages/20220729234942_hu9149058069851839611.webp differ diff --git a/posts/deploy-blog-to-cloudflare-pages/index.html b/posts/deploy-blog-to-cloudflare-pages/index.html new file mode 100644 index 000000000..2ae7878a5 --- /dev/null +++ b/posts/deploy-blog-to-cloudflare-pages/index.html @@ -0,0 +1,20 @@ +将博客部署到Cloudflare Pages | 流动 +

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明

      缘由

      Github Pages部署有一个问题,就是不支持HSTS

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      20220729232638

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      20220729234321

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      部署

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      20220729234310

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      采用拉取master构建好的文件的话,只需要7秒左右。

      20220729234651

      补齐Header头

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      20220729234942

      至此HSTS搞定。

      HSTS资料

      💬评论
      \ No newline at end of file diff --git a/posts/deploy-blog-to-ipfs/20230222080123.png b/posts/deploy-blog-to-ipfs/20230222080123.png new file mode 100644 index 000000000..c2057d314 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222080123.png differ diff --git a/posts/deploy-blog-to-ipfs/20230222080123_hu13211350781374683451.webp b/posts/deploy-blog-to-ipfs/20230222080123_hu13211350781374683451.webp new file mode 100644 index 000000000..bad164ab9 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222080123_hu13211350781374683451.webp differ diff --git a/posts/deploy-blog-to-ipfs/20230222080123_hu4596934888818164050.png b/posts/deploy-blog-to-ipfs/20230222080123_hu4596934888818164050.png new file mode 100644 index 000000000..949d49b19 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222080123_hu4596934888818164050.png differ diff --git a/posts/deploy-blog-to-ipfs/20230222122431.png b/posts/deploy-blog-to-ipfs/20230222122431.png new file mode 100644 index 000000000..5429c449c Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222122431.png differ diff --git a/posts/deploy-blog-to-ipfs/20230222122431_hu12401245943987982752.webp b/posts/deploy-blog-to-ipfs/20230222122431_hu12401245943987982752.webp new file mode 100644 index 000000000..4564ea3f2 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222122431_hu12401245943987982752.webp differ diff --git a/posts/deploy-blog-to-ipfs/20230222122431_hu13651568124106455628.jpg b/posts/deploy-blog-to-ipfs/20230222122431_hu13651568124106455628.jpg new file mode 100644 index 000000000..9dbb67322 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222122431_hu13651568124106455628.jpg differ diff --git a/posts/deploy-blog-to-ipfs/20230222122431_hu2718061129469094864.jpg b/posts/deploy-blog-to-ipfs/20230222122431_hu2718061129469094864.jpg new file mode 100644 index 000000000..312663d2e Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222122431_hu2718061129469094864.jpg differ diff --git a/posts/deploy-blog-to-ipfs/20230222122431_hu4701902175765317424.webp b/posts/deploy-blog-to-ipfs/20230222122431_hu4701902175765317424.webp new file mode 100644 index 000000000..005ea18e6 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/20230222122431_hu4701902175765317424.webp differ diff --git a/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs.png b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs.png new file mode 100644 index 000000000..a945f6808 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs.png differ diff --git a/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu14186891486412235748.webp b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu14186891486412235748.webp new file mode 100644 index 000000000..e817dfd4d Binary files /dev/null and b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu14186891486412235748.webp differ diff --git a/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu2620749431992014705.png b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu2620749431992014705.png new file mode 100644 index 000000000..c65a8f1c9 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/deploy-blog-to-ipfs_hu2620749431992014705.png differ diff --git a/posts/deploy-blog-to-ipfs/dns-record.png b/posts/deploy-blog-to-ipfs/dns-record.png new file mode 100644 index 000000000..0adf9f415 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/dns-record.png differ diff --git a/posts/deploy-blog-to-ipfs/dns-record_hu2533669715578871390.png b/posts/deploy-blog-to-ipfs/dns-record_hu2533669715578871390.png new file mode 100644 index 000000000..9a1b89cff Binary files /dev/null and b/posts/deploy-blog-to-ipfs/dns-record_hu2533669715578871390.png differ diff --git a/posts/deploy-blog-to-ipfs/dns-record_hu6236716241678277330.webp b/posts/deploy-blog-to-ipfs/dns-record_hu6236716241678277330.webp new file mode 100644 index 000000000..13e360f32 Binary files /dev/null and b/posts/deploy-blog-to-ipfs/dns-record_hu6236716241678277330.webp differ diff --git a/posts/deploy-blog-to-ipfs/index.html b/posts/deploy-blog-to-ipfs/index.html new file mode 100644 index 000000000..e1ff92ee6 --- /dev/null +++ b/posts/deploy-blog-to-ipfs/index.html @@ -0,0 +1,97 @@ +将博客部署到星际文件系统(IPFS) | 流动 +

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      照惯例,先上演示.访问我的IPFS博客

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      1. Cloudflare帐号
      2. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      3. 一个域名

      方案介绍:

      实现方案

      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      3. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      4. 绑定域名到IPNS地址,通过域名访问IPFS文件.

      1. 部署IPFS服务

      • 安装kubo,详见官方文档

        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • 初始化IPFS

        ipfs init --profile=server
        +
      • 添加到开机启动

        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        启动进程.

        systemctl start ipfs.service
        +
      • 开放端口

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

      2. GitHub Actions配置

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      完整的工作流配置见main.yml

      • 添加如下变量到Actions secrets

        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • 更新yaml配置文件,添加如下任务.

           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

      3. 域名配置

      Cloudflare上添加解析:

      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.

      DNS解析

      4. 开启相对路径

      Livid大佬提醒,公共网关访问时存在相对路径问题

      我用的Hugo,配置文件里打开relativeURLs配置。

      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      VPS主机运行情况

      两天跑了14G流量,每月的流量资源包基本够用了.

      参考资料:

      IPFS 日用优化指南

      参考配置

      💬评论
      \ No newline at end of file diff --git a/posts/deploy-twikoo-on-netlify/20231019190935.png b/posts/deploy-twikoo-on-netlify/20231019190935.png new file mode 100644 index 000000000..c81eda26d Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019190935.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019190935_hu11946659690224258165.webp b/posts/deploy-twikoo-on-netlify/20231019190935_hu11946659690224258165.webp new file mode 100644 index 000000000..3d6711e13 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019190935_hu11946659690224258165.webp differ diff --git a/posts/deploy-twikoo-on-netlify/20231019190935_hu3943517669446501926.png b/posts/deploy-twikoo-on-netlify/20231019190935_hu3943517669446501926.png new file mode 100644 index 000000000..f59997432 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019190935_hu3943517669446501926.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191326.png b/posts/deploy-twikoo-on-netlify/20231019191326.png new file mode 100644 index 000000000..690de0a7e Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191326.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191326_hu2551903595337723990.png b/posts/deploy-twikoo-on-netlify/20231019191326_hu2551903595337723990.png new file mode 100644 index 000000000..64681fb17 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191326_hu2551903595337723990.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191326_hu8838313001624003405.webp b/posts/deploy-twikoo-on-netlify/20231019191326_hu8838313001624003405.webp new file mode 100644 index 000000000..1553b9369 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191326_hu8838313001624003405.webp differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191522.png b/posts/deploy-twikoo-on-netlify/20231019191522.png new file mode 100644 index 000000000..0ee980ef9 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191522.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191522_hu11483385048471464107.png b/posts/deploy-twikoo-on-netlify/20231019191522_hu11483385048471464107.png new file mode 100644 index 000000000..f2e375304 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191522_hu11483385048471464107.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191522_hu5359049959332663132.webp b/posts/deploy-twikoo-on-netlify/20231019191522_hu5359049959332663132.webp new file mode 100644 index 000000000..0857974c5 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191522_hu5359049959332663132.webp differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191833.png b/posts/deploy-twikoo-on-netlify/20231019191833.png new file mode 100644 index 000000000..1053ec6f8 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191833.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191833_hu15007640604031802936.png b/posts/deploy-twikoo-on-netlify/20231019191833_hu15007640604031802936.png new file mode 100644 index 000000000..805fb8229 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191833_hu15007640604031802936.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019191833_hu7525574806016549572.webp b/posts/deploy-twikoo-on-netlify/20231019191833_hu7525574806016549572.webp new file mode 100644 index 000000000..1dc250d91 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019191833_hu7525574806016549572.webp differ diff --git a/posts/deploy-twikoo-on-netlify/20231019192158.png b/posts/deploy-twikoo-on-netlify/20231019192158.png new file mode 100644 index 000000000..e58b04456 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019192158.png differ diff --git a/posts/deploy-twikoo-on-netlify/20231019192158_hu4392240285882896864.webp b/posts/deploy-twikoo-on-netlify/20231019192158_hu4392240285882896864.webp new file mode 100644 index 000000000..ecbf5222a Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019192158_hu4392240285882896864.webp differ diff --git a/posts/deploy-twikoo-on-netlify/20231019192158_hu696688395608897294.png b/posts/deploy-twikoo-on-netlify/20231019192158_hu696688395608897294.png new file mode 100644 index 000000000..e4c54d708 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/20231019192158_hu696688395608897294.png differ diff --git a/posts/deploy-twikoo-on-netlify/index.html b/posts/deploy-twikoo-on-netlify/index.html new file mode 100644 index 000000000..628596f96 --- /dev/null +++ b/posts/deploy-twikoo-on-netlify/index.html @@ -0,0 +1,118 @@ +在Netlify上部署Twikoo评论系统 | 流动 +

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      背景

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      部署

      参考官网文档,部署即可。

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      Netlify部署Twikoo

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      部署后,通过https://comment.liudon.com访问,返回200。

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      CORS报错

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      404报错

      经过一番定位,发现了问题所在:

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      twikoo-netlify根目录返回

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      twikoo-vercel转发

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      twikoo环境id

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      这下访问彻底ok了。

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      搜索一番后,Twikoo已经给出了更新操作:

      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      对于一个程序员,我们的追求就是自动化。

      自动版本更新

      1. 服务端版本自动更新

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      1. 博客引用版本自动更新

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      • 引用版本配置化

      comments.html文件修改:

      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
      • 获取最新版本号部署

      Actions新增步骤:

          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      💬评论
      \ No newline at end of file diff --git a/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d.png b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d.png new file mode 100644 index 000000000..d086765d0 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d.png differ diff --git a/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu16486534993597105028.png b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu16486534993597105028.png new file mode 100644 index 000000000..b07250b79 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu16486534993597105028.png differ diff --git a/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu8628832215964479861.webp b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu8628832215964479861.webp new file mode 100644 index 000000000..0596daf80 Binary files /dev/null and b/posts/deploy-twikoo-on-netlify/netlify-3.4f565e5d_hu8628832215964479861.webp differ diff --git a/posts/fix-blog-cls/cos-img-process.png b/posts/fix-blog-cls/cos-img-process.png new file mode 100644 index 000000000..0881c0ce8 Binary files /dev/null and b/posts/fix-blog-cls/cos-img-process.png differ diff --git a/posts/fix-blog-cls/cos-img-process_hu1983331479586429135.png b/posts/fix-blog-cls/cos-img-process_hu1983331479586429135.png new file mode 100644 index 000000000..5576f74af Binary files /dev/null and b/posts/fix-blog-cls/cos-img-process_hu1983331479586429135.png differ diff --git a/posts/fix-blog-cls/cos-img-process_hu3482055803781631215.webp b/posts/fix-blog-cls/cos-img-process_hu3482055803781631215.webp new file mode 100644 index 000000000..c3f19ad8b Binary files /dev/null and b/posts/fix-blog-cls/cos-img-process_hu3482055803781631215.webp differ diff --git a/posts/fix-blog-cls/cover-code.png b/posts/fix-blog-cls/cover-code.png new file mode 100644 index 000000000..ba777a630 Binary files /dev/null and b/posts/fix-blog-cls/cover-code.png differ diff --git a/posts/fix-blog-cls/cover-code_hu12274468346048023657.webp b/posts/fix-blog-cls/cover-code_hu12274468346048023657.webp new file mode 100644 index 000000000..0b3cd8bf7 Binary files /dev/null and b/posts/fix-blog-cls/cover-code_hu12274468346048023657.webp differ diff --git a/posts/fix-blog-cls/cover-code_hu1742885001261139749.png b/posts/fix-blog-cls/cover-code_hu1742885001261139749.png new file mode 100644 index 000000000..f1646e025 Binary files /dev/null and b/posts/fix-blog-cls/cover-code_hu1742885001261139749.png differ diff --git a/posts/fix-blog-cls/figure-code.png b/posts/fix-blog-cls/figure-code.png new file mode 100644 index 000000000..d73965f1f Binary files /dev/null and b/posts/fix-blog-cls/figure-code.png differ diff --git a/posts/fix-blog-cls/figure-code_hu10475064462256053076.png b/posts/fix-blog-cls/figure-code_hu10475064462256053076.png new file mode 100644 index 000000000..b2b4e183b Binary files /dev/null and b/posts/fix-blog-cls/figure-code_hu10475064462256053076.png differ diff --git a/posts/fix-blog-cls/figure-code_hu17776232354864714128.webp b/posts/fix-blog-cls/figure-code_hu17776232354864714128.webp new file mode 100644 index 000000000..382f736d3 Binary files /dev/null and b/posts/fix-blog-cls/figure-code_hu17776232354864714128.webp differ diff --git a/posts/fix-blog-cls/gtmetrix-result.png b/posts/fix-blog-cls/gtmetrix-result.png new file mode 100644 index 000000000..771196432 Binary files /dev/null and b/posts/fix-blog-cls/gtmetrix-result.png differ diff --git a/posts/fix-blog-cls/gtmetrix-result_hu10689073513855308722.webp b/posts/fix-blog-cls/gtmetrix-result_hu10689073513855308722.webp new file mode 100644 index 000000000..87e5968b2 Binary files /dev/null and b/posts/fix-blog-cls/gtmetrix-result_hu10689073513855308722.webp differ diff --git a/posts/fix-blog-cls/gtmetrix-result_hu16088222964268984721.png b/posts/fix-blog-cls/gtmetrix-result_hu16088222964268984721.png new file mode 100644 index 000000000..9253ce4fb Binary files /dev/null and b/posts/fix-blog-cls/gtmetrix-result_hu16088222964268984721.png differ diff --git a/posts/fix-blog-cls/index.html b/posts/fix-blog-cls/index.html new file mode 100644 index 000000000..a872475d4 --- /dev/null +++ b/posts/fix-blog-cls/index.html @@ -0,0 +1,50 @@ +优化博客的累计布局偏移(CLS)问题 | 流动 +

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      问题表现

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      Lighthouse测试报告

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      为了解决这个问题,需要指定封面的宽高参数。

      cover.html code

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      解决方案

      1. 新增封面配置

        文章封面配置新增widthheight属性。

        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. 自定义封面文件

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        cos-img-process

      3. 新增css配置

        新增如下配置,否则会导致图片变形。

        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +

      再进一步

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      基于markdown语法的图片代码,是不支持宽高参数的。

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      figure.html code

      我们使用figure语法插入图片,指定图片宽高。

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      gtmetrix-result

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      💬评论
      \ No newline at end of file diff --git a/posts/fix-blog-cls/lighthouse_result.png b/posts/fix-blog-cls/lighthouse_result.png new file mode 100644 index 000000000..b0373914b Binary files /dev/null and b/posts/fix-blog-cls/lighthouse_result.png differ diff --git a/posts/fix-blog-cls/lighthouse_result_hu3432421370760490344.png b/posts/fix-blog-cls/lighthouse_result_hu3432421370760490344.png new file mode 100644 index 000000000..e29f8f5c2 Binary files /dev/null and b/posts/fix-blog-cls/lighthouse_result_hu3432421370760490344.png differ diff --git a/posts/fix-blog-cls/lighthouse_result_hu5907100223710448475.webp b/posts/fix-blog-cls/lighthouse_result_hu5907100223710448475.webp new file mode 100644 index 000000000..c1f1d5511 Binary files /dev/null and b/posts/fix-blog-cls/lighthouse_result_hu5907100223710448475.webp differ diff --git a/posts/fix-hugo-json-feed/202303251415675.png b/posts/fix-hugo-json-feed/202303251415675.png new file mode 100644 index 000000000..316661892 Binary files /dev/null and b/posts/fix-hugo-json-feed/202303251415675.png differ diff --git a/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp b/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp new file mode 100644 index 000000000..cfea9442c Binary files /dev/null and b/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp differ diff --git a/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png b/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png new file mode 100644 index 000000000..2496a869d Binary files /dev/null and b/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png differ diff --git a/posts/fix-hugo-json-feed/index.html b/posts/fix-hugo-json-feed/index.html new file mode 100644 index 000000000..603002e0e --- /dev/null +++ b/posts/fix-hugo-json-feed/index.html @@ -0,0 +1,180 @@ +修正Hugo的JSON Feed格式 | 流动 +

      修正Hugo的JSON Feed格式

      问题背景

      前几天在Planet里follow自己的web3博客,遇到下面的错误。

      PlanetFeedError

      经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。

      因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。

      [
      +  {
      +    "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n",
      +    "permalink": "https://dvel.me/posts/chd-quiz-answer/",
      +    "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!",
      +    "title": "CHD 油猴脚本:每日签到自动答题"
      +  },
      +  ...
      +]
      +

      下面是一个JSON Feed的示例,详细规范见jsonfeed.org

      {
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": "My Example Feed",
      +    "home_page_url": "https://example.org/",
      +    "feed_url": "https://example.org/feed.json",
      +    "items": [
      +        {
      +            "id": "2",
      +            "content_text": "This is a second item.",
      +            "url": "https://example.org/second-item"
      +        },
      +        {
      +            "id": "1",
      +            "content_html": "<p>Hello, world!</p>",
      +            "url": "https://example.org/initial-post"
      +        }
      +    ]
      +}
      +

      修复方案

      1. 添加自定义jsonfeed模版文件,路径为layouts/_default/index.jsonfeed.json

      文件内容如下:

      {{- $pctx := . -}}
      +{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}
      +{{- $pages := slice -}}
      +{{- if or $.IsHome $.IsSection -}}
      +{{- $pages = $pctx.RegularPages -}}
      +{{- else -}}
      +{{- $pages = $pctx.Pages -}}
      +{{- end -}}
      +{{- $limit := site.Config.Services.RSS.Limit -}}
      +{{- if ge $limit 1 -}}
      +{{- $pages = $pages | first $limit -}}
      +{{- end -}}
      +{{- $title := "" }}
      +{{- if eq .Title .Site.Title }}
      +{{- $title = .Site.Title }}
      +{{- else }}
      +{{- with .Title }}
      +{{- $title = print . " on "}}
      +{{- end }}
      +{{- $title = print $title .Site.Title }}
      +{{- end }}
      +{
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": {{ $title | jsonify }},
      +    "home_page_url": {{ .Permalink | jsonify }},
      +    {{- with  .OutputFormats.Get "jsonfeed" }}
      +    "feed_url": {{ .Permalink | jsonify  }},
      +    {{- end }}
      +    {{- if (or .Site.Params.author .Site.Params.author_url) }}
      +    "authors": [{
      +      {{- if .Site.Params.author }}
      +        "name": {{ .Site.Params.author | jsonify }},
      +      {{- end }}
      +      {{- if .Site.Params.author_url }}
      +        "url": {{ .Site.Params.author_url | jsonify }}
      +      {{- end }}
      +    }],
      +    {{- end }}
      +    {{- if $pages }}
      +    "items": [
      +        {{- range $index, $element := $pages }}
      +        {{- with $element }}
      +        {{- if $index }},{{end}} {
      +            "title": {{ .Title | jsonify }},
      +            "id": {{ .Permalink | jsonify }},
      +            "url": {{ .Permalink | jsonify }},
      +            {{- if .Site.Params.showFullTextinJSONFeed }}
      +            "summary": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            "content_html": {{ .Content | jsonify }},
      +            {{- else }}
      +            "content_text": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            {{- end }}
      +            {{- if .Params.cover.image }}
      +            {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
      +            {{- if $cover }}
      +            "image": {{ (path.Join .RelPermalink $cover) | absURL | jsonify }},
      +            {{- end }}
      +            {{- end }}
      +            "date_published": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
      +            {{- $tags := slice -}}
      +            {{ with .Params.tags }}
      +            {{ range . }}
      +            {{ $tags = $tags | append (. | jsonify) }}
      +            {{end}}
      +            {{end}}
      +            "tags": [{{ delimit $tags ", " }}]
      +        }
      +        {{- end }}
      +        {{- end }}
      +    ]
      +    {{ end }}
      +}
      +
      1. 开启JSON Feed

      配置文件调整如下:

      outputFormats:
      +  jsonfeed: # 添加jsonfeed输出格式
      +    mediaType: application/feed+json
      +    baseName: feed
      +    rel: alternate
      +    isPlainText: true
      +
      +outputs:
      +    home:
      +        - HTML
      +        - RSS
      +        - json # fusejs搜索依赖index.json,不要漏掉这个配置
      +        - jsonfeed # 开启jsonfeed
      +
      +params:
      +    ...
      +
      +    showFullTextinJSONFeed: true # jsonfeed开启全文输出
      +

      参考资料:How to add JSON Feed support to Hugo

      💬评论
      \ No newline at end of file diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j.jpg new file mode 100644 index 000000000..c0075bd1a Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu11889884118258270666.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu11889884118258270666.jpg new file mode 100644 index 000000000..29b60af31 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu11889884118258270666.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu17946149840019200338.webp b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu17946149840019200338.webp new file mode 100644 index 000000000..663df1626 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igknr8lyj21ae09874j_hu17946149840019200338.webp differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7.jpg new file mode 100644 index 000000000..ee24a477d Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu15031826398138036045.webp b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu15031826398138036045.webp new file mode 100644 index 000000000..ebee75196 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu15031826398138036045.webp differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu18129525753661784040.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu18129525753661784040.jpg new file mode 100644 index 000000000..5f17a88c0 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1igoreyi9j20oz04mdg7_hu18129525753661784040.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r.jpg new file mode 100644 index 000000000..67df9c4a7 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu412242710392457856.webp b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu412242710392457856.webp new file mode 100644 index 000000000..6f1990bb0 Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu412242710392457856.webp differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu9144468641138631652.jpg b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu9144468641138631652.jpg new file mode 100644 index 000000000..1067be93f Binary files /dev/null and b/posts/flink-could-not-resolve-resourcemanager-address/63c9befaly1g1ih665ffjj21a709374r_hu9144468641138631652.jpg differ diff --git a/posts/flink-could-not-resolve-resourcemanager-address/index.html b/posts/flink-could-not-resolve-resourcemanager-address/index.html new file mode 100644 index 000000000..ba17bfd0c --- /dev/null +++ b/posts/flink-could-not-resolve-resourcemanager-address/index.html @@ -0,0 +1,40 @@ +Flink Could Not Resolve Resourcemanager Address | 流动 +

      Flink Could Not Resolve Resourcemanager Address

      什么是Flink。

      Apache Flink® - Stateful Computations over Data Streams

      Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。

      这里使用单机模式

      问题表现

      启动Flink

      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ./bin/start-cluster.sh 
      +Starting cluster.
      +Starting standalonesession daemon on host VM_80_180_centos.
      +Starting taskexecutor daemon on host VM_80_180_centos.
      +

      查看进程

      [root@VM_80_180_centos /usr/local/flink-1.7.2]# jps
      +10442 StandaloneSessionClusterEntrypoint
      +11067 Jps
      +10909 TaskManagerRunner
      +[root@VM_80_180_centos /usr/local/flink-1.7.2]# 
      +

      查看日志,发现"Could not resolve ResourceManager address"的错误。

      [root@VM_80_180_centos /usr/local/flink-1.7.2]# tail -f log/flink-root-taskexecutor-*.log
      +
      +2019-03-27 19:43:23,804 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +2019-03-27 19:43:43,843 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +

      访问Flink的web页面,发现task数全为0.

      flink no task

      问题原因:

      taskmanager.host

      Flink的taskmanager.host默认为空,会使用hostname。

      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ping VM_80_180_centos
      +PING VM_80_180_centos (100.125.80.180) 56(84) bytes of data.
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=1 ttl=64 time=0.022 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=2 ttl=64 time=0.038 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=3 ttl=64 time=0.038 ms
      +

      Flink的jobmanager.host默认为localhost。

      这里jobmanager和taskmanager绑定的ip不一样,导致出错。

      解决办法:

      vim conf/flink-conf.yaml
      +
      +添加下面这行配置
      +taskmanager.host: localhost
      +

      保存退出,然后重新启动Flink,这个时候在web端就可以看到有可用task了。

      flink web

      💬评论
      \ No newline at end of file diff --git a/posts/github-pages-deployment-tutorial/20240924-214938.png b/posts/github-pages-deployment-tutorial/20240924-214938.png new file mode 100644 index 000000000..b361bd252 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-214938.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-214938_hu482132360433279231.webp b/posts/github-pages-deployment-tutorial/20240924-214938_hu482132360433279231.webp new file mode 100644 index 000000000..7cca38a3b Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-214938_hu482132360433279231.webp differ diff --git a/posts/github-pages-deployment-tutorial/20240924-214938_hu7283675988670190119.png b/posts/github-pages-deployment-tutorial/20240924-214938_hu7283675988670190119.png new file mode 100644 index 000000000..cc1ef4211 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-214938_hu7283675988670190119.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-215958.png b/posts/github-pages-deployment-tutorial/20240924-215958.png new file mode 100644 index 000000000..5f5544434 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-215958.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-215958_hu18258395951143914551.png b/posts/github-pages-deployment-tutorial/20240924-215958_hu18258395951143914551.png new file mode 100644 index 000000000..e9fc2da86 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-215958_hu18258395951143914551.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-215958_hu7195499678805036272.webp b/posts/github-pages-deployment-tutorial/20240924-215958_hu7195499678805036272.webp new file mode 100644 index 000000000..b4107a5cd Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-215958_hu7195499678805036272.webp differ diff --git a/posts/github-pages-deployment-tutorial/20240924-220607.png b/posts/github-pages-deployment-tutorial/20240924-220607.png new file mode 100644 index 000000000..a0b3d2082 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-220607.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-220607_hu12933780071938403223.png b/posts/github-pages-deployment-tutorial/20240924-220607_hu12933780071938403223.png new file mode 100644 index 000000000..3d20911c2 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-220607_hu12933780071938403223.png differ diff --git a/posts/github-pages-deployment-tutorial/20240924-220607_hu5032447485174126128.webp b/posts/github-pages-deployment-tutorial/20240924-220607_hu5032447485174126128.webp new file mode 100644 index 000000000..d03ca30f0 Binary files /dev/null and b/posts/github-pages-deployment-tutorial/20240924-220607_hu5032447485174126128.webp differ diff --git a/posts/github-pages-deployment-tutorial/index.html b/posts/github-pages-deployment-tutorial/index.html new file mode 100644 index 000000000..aa54a33cf --- /dev/null +++ b/posts/github-pages-deployment-tutorial/index.html @@ -0,0 +1,63 @@ +Github Pages 部署流程解析 | 流动 +

      Github Pages 部署流程解析

      上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。

      看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。

          - name: Install dependencies
      +    run: pnpm install
      +    - name: Build
      +    run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build
      +
      +    - name: Upload artifact
      +    uses: actions/upload-pages-artifact@v3
      +    with:
      +        # Upload dist repository
      +        path: './dist'
      +    - name: Deploy to GitHub Pages
      +    id: deployment
      +    uses: actions/deploy-pages@v4
      +

      核心逻辑就是上面这段。

      之前搞过部署hugo静态站点到Github Pages,使用的分支方式部署,编译后的静态文件单独用一个分支存放。

      这里以我自己的博客项目举例,大致流程如下图:

      github-pages-deploy-flow

      按我的理解,这里最终访问的文件内容是存在gh-page分支下的。

      但是实际部署完running_page项目后,我发现并没有出现gh-page分支,但是Github Pages却可以正常访问。

      有点不可思议,这个访问的数据是在哪里的呢?

      带着这个疑问,在v2ex上发了个咨询贴

      经过网友解惑,大致搞明白了这里的流程:

      github-pages-deploy-flow

      Github Pages的发布源有两种方式,通过分支部署和通过Github Actions部署,分别对应上图的两条分支。

      这里最终都会将build后的静态文件部署到Github Pages服务上,供用户访问。

      分支部署的方式,其实是有一个内置工作流部署到Github Pages服务上的。

      整个部署流程大致就是这样,最终的静态文件都是存在Github Pages服务上的。

      💬评论
      \ No newline at end of file diff --git a/posts/golang-go-get-binary-not-found-solution/index.html b/posts/golang-go-get-binary-not-found-solution/index.html new file mode 100644 index 000000000..8dff534f2 --- /dev/null +++ b/posts/golang-go-get-binary-not-found-solution/index.html @@ -0,0 +1,56 @@ +解决Golang使用go get安装包后找不到可执行文件的问题 | 流动 +

      解决Golang使用go get安装包后找不到可执行文件的问题

      背景

      编译流水线代码

      go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +
      +protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto
      +
      +...
      +
      +go build -o xxx
      +

      在go升级到1.20.1版本后,执行报错。

      protoc-gen-go: program not found or is not executable
      +

      解决

      Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.

      In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.

      https://docs.studygolang.com/doc/go-get-install-deprecation

      从 Go 1.7 版本开始,go get 命令默认只会下载包,不会自动编译和安装可执行文件。

      因此,如果你想要使用 go get 命令安装包并编译可执行文件,你需要使用 go install 命令。

      替换为go install解决。

      💬评论
      \ No newline at end of file diff --git "a/posts/golang\350\247\243\346\236\220json\347\232\204\344\270\200\344\270\252\351\227\256\351\242\230/index.html" "b/posts/golang\350\247\243\346\236\220json\347\232\204\344\270\200\344\270\252\351\227\256\351\242\230/index.html" new file mode 100644 index 000000000..641b42549 --- /dev/null +++ "b/posts/golang\350\247\243\346\236\220json\347\232\204\344\270\200\344\270\252\351\227\256\351\242\230/index.html" @@ -0,0 +1,172 @@ +Golang解析json的一个问题 | 流动 +

      Golang解析json的一个问题

      业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题:

      请求接口,按返回包字段判断请求成功与否。
      +

      伪代码如下:

      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code int    `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 场景1,返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +    // 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	// 场景2,返回包不符合接口要求,缺少相关字段
      +	str = `{"retCode":100,"retMsg":"failed"}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +    // 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +}
      +

      这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。

      缺少code字段,Unmarshal解析后,按默认值处理,所以code为0,导致验证出错。

      修正方案:

      Code字段定义为引用类型,通过判断地址是否为nil来区分缺少字段的情况。

      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code *int   `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +	// 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	if res.Code == nil || *res.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +
      +	// 返回包不符合接口要求,缺少相关字段
      +	str = `{"id":1}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +	// 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +
      +	if res1.Code == nil || *res1.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +}
      +

      参考资料:

      how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided

      json-key-not-set-null-golang

      💬评论
      \ No newline at end of file diff --git a/posts/gorm-supports-sqlcommenter/index.html b/posts/gorm-supports-sqlcommenter/index.html new file mode 100644 index 000000000..637eaea76 --- /dev/null +++ b/posts/gorm-supports-sqlcommenter/index.html @@ -0,0 +1,161 @@ +GORM增加sqlcommenter特性 | 流动 +

      GORM增加sqlcommenter特性

      什么是sqlcommenter?

      sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

      GORM提供了hints组件,可以支持sqlcommenter

      import "gorm.io/hints"
      +
      +DB.Clauses(hints.Comment("select", "master")).Find(&User{})
      +// SELECT /*master*/ * FROM `users`;
      +
      +DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
      +// SELECT * FROM `users` WHERE id = ? /* hint */
      +

      但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

      我希望是全局增加sqlcommenter,业务侧不需要过多调整。

      完整代码如下:

      plugins/gorm.go
      +
      +package plugins
      +
      +import (
      +	"fmt"
      +
      +	gorm "gorm.io/gorm"
      +	gormclause "gorm.io/gorm/clause"
      +)
      +
      +type Comment struct {
      +	Content string
      +}
      +
      +func (c Comment) Name() string {
      +	return "COMMENT"
      +}
      +
      +func (c Comment) Build(builder gormclause.Builder) {
      +	builder.WriteString("/* ")
      +	builder.WriteString(c.Content)
      +	builder.WriteString(" */")
      +}
      +
      +func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
      +}
      +
      +func (c Comment) ModifyStatement(stmt *gorm.Statement) {
      +	clause := stmt.Clauses[c.Name()]
      +    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
      +    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
      +	clause.Expression = c
      +	stmt.Clauses[c.Name()] = clause
      +}
      +
      +var extraClause = []string{"COMMENT"}
      +
      +type CommentClausePlugin struct{}
      +
      +// NewCommentClausePlugin create a new ExtraPlugin
      +func NewCommentClausePlugin() *CommentClausePlugin {
      +	return &CommentClausePlugin{}
      +}
      +
      +// Name plugin name
      +func (ep *CommentClausePlugin) Name() string {
      +	return "CommentClausePlugin"
      +}
      +
      +// Initialize register BuildClauses
      +func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
      +	initClauses(db)
      +	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)
      +
      +	return
      +}
      +
      +func AddAnnotation(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +
      +	rid := "xx"
      +	// context上下文里取rid信息
      +	if v, ok := db.Statement.Context.Value("rid").(string); ok {
      +		rid = v
      +	}
      +
      +	content := fmt.Sprintf("rid=%s", rid)
      +
      +	if db.Statement.SQL.Len() > 0 {
      +		oldSQL := db.Statement.SQL.String()
      +		db.Statement.SQL.Reset()
      +		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
      +		return
      +	}
      +
      +	db.Statement.AddClause(Comment{Content: content})
      +}
      +
      +// initClauses init SQL clause
      +func initClauses(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +	createClause := append(extraClause, db.Callback().Create().Clauses...)
      +	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
      +	queryClause := append(extraClause, db.Callback().Query().Clauses...)
      +	updateClause := append(extraClause, db.Callback().Update().Clauses...)
      +	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
      +	rowClause := append(extraClause, db.Callback().Row().Clauses...)
      +	db.Callback().Create().Clauses = createClause
      +	db.Callback().Delete().Clauses = deleteClause
      +	db.Statement.Callback().Query().Clauses = queryClause
      +	db.Callback().Update().Clauses = updateClause
      +	db.Callback().Raw().Clauses = rawClause
      +	db.Callback().Row().Clauses = rowClause
      +}
      +
      +
      +main.go
      +package main
      +
      +import (
      +    "context"
      +    "plugins"
      +
      +    gorm "gorm.io/gorm"
      +    "github.com/google/uuid"
      +)
      +
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +
      +    db.Use(plugins.NewCommentClausePlugin())
      +
      +    db.Create(&Product{Code: "D42", Price: 100})
      +
      +    // 传入context,指定rid
      +    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
      +    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
      +}
      +

      阻塞了两天的问题,终于解决了!😁😁😁

      how gorm generates sql

      💬评论
      \ No newline at end of file diff --git a/posts/hi-follow/20240917-010236.png b/posts/hi-follow/20240917-010236.png new file mode 100644 index 000000000..8448bb60f Binary files /dev/null and b/posts/hi-follow/20240917-010236.png differ diff --git a/posts/hi-follow/20240917-010236_hu1994357333343083286.webp b/posts/hi-follow/20240917-010236_hu1994357333343083286.webp new file mode 100644 index 000000000..167f56b16 Binary files /dev/null and b/posts/hi-follow/20240917-010236_hu1994357333343083286.webp differ diff --git a/posts/hi-follow/20240917-010236_hu2418251097890743445.png b/posts/hi-follow/20240917-010236_hu2418251097890743445.png new file mode 100644 index 000000000..8105688da Binary files /dev/null and b/posts/hi-follow/20240917-010236_hu2418251097890743445.png differ diff --git a/posts/hi-follow/20240917-012132.png b/posts/hi-follow/20240917-012132.png new file mode 100644 index 000000000..070323918 Binary files /dev/null and b/posts/hi-follow/20240917-012132.png differ diff --git a/posts/hi-follow/20240917-012132_hu3857577152063773852.png b/posts/hi-follow/20240917-012132_hu3857577152063773852.png new file mode 100644 index 000000000..efa24a24a Binary files /dev/null and b/posts/hi-follow/20240917-012132_hu3857577152063773852.png differ diff --git a/posts/hi-follow/20240917-012132_hu5078466684681765960.webp b/posts/hi-follow/20240917-012132_hu5078466684681765960.webp new file mode 100644 index 000000000..05b4a1147 Binary files /dev/null and b/posts/hi-follow/20240917-012132_hu5078466684681765960.webp differ diff --git a/posts/hi-follow/index.html b/posts/hi-follow/index.html new file mode 100644 index 000000000..89a3a863f --- /dev/null +++ b/posts/hi-follow/index.html @@ -0,0 +1,21 @@ +你好 Follow | 流动 +

      你好 Follow

      Follow: Next generation information browser.

      最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。

      蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。

      上周五好不容易加上管理员,要到了一枚邀请码,终于可以激活体验了。

      Follow里,订阅变的异常简单,输入url,它会自己检查rss订阅。

      Follow

      另外发现我的博客,在Follow显示的内容不全。

      检查了一下,发现是输出的RSS内容不全。

      修改hugo配置文件,开启RSS输出全文。

      ShowFullTextinRSS: true
      +

      从木木大佬那里看到,可以认证自己的Feed,我也来搞一下我的。

      This message is used to verify that this feed (feedId:55815884011044914) belongs to me (userId:56204227179125760). 
      +Join me in enjoying the next generation information browser https://follow.is.
      +

      咱也是带标的了。

      认证

      💬评论
      \ No newline at end of file diff --git a/posts/how-gorm-generates-sql/index.html b/posts/how-gorm-generates-sql/index.html new file mode 100644 index 000000000..f9556d0b0 --- /dev/null +++ b/posts/how-gorm-generates-sql/index.html @@ -0,0 +1,772 @@ +源码分析:GORM是如何生成sql的 | 流动 +

      源码分析:GORM是如何生成sql的

      gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。

      gorm使用示例

      package main
      +
      +import (
      +  "gorm.io/driver/mysql"
      +  "gorm.io/gorm"
      +)
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
      +  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +  
      +  var product Product
      +  db.First(&product, 1) // 根据整型主键查找
      +}
      +

      我们以First查询为例,看一下是怎么转成具体sql的。

      finisher_api.go文件,声明了First方法。

      // First finds the first record ordered by primary key, matching given conditions conds
      +func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
      +	// 注册Order类型的Clause
      +	tx = db.Limit(1).Order(clause.OrderByColumn{
      +		Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
      +	})
      +	// 这里如果有指定条件的话,注册一个Where类型的Clause
      +	if len(conds) > 0 {
      +		if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
      +			tx.Statement.AddClause(clause.Where{Exprs: exprs})
      +		}
      +	}
      +	tx.Statement.RaiseErrorOnNotFound = true
      +	tx.Statement.Dest = dest
      +	return tx.callbacks.Query().Execute(tx)
      +}
      +

      gorm.go文件,可以找到tx.callbacks定义。

      type Config struct {
      +	...
      +
      +	callbacks  *callbacks
      +	cacheStore *sync.Map
      +}
      +

      callbacks.go

      // callbacks gorm callbacks manager
      +type callbacks struct {
      +	processors map[string]*processor
      +}
      +
      +type processor struct {
      +	db        *DB
      +	Clauses   []string
      +	fns       []func(*DB)
      +	callbacks []*callback
      +}
      +
      +type callback struct {
      +	name      string
      +	before    string
      +	after     string
      +	remove    bool
      +	replace   bool
      +	match     func(*DB) bool
      +	handler   func(*DB)
      +	processor *processor
      +}
      +
      +// 返回query类型的processor
      +func (cs *callbacks) Query() *processor {
      +	return cs.processors["query"]
      +}
      +
      +func (p *processor) Execute(db *DB) *DB {
      +	// call scopes
      +	for len(db.Statement.scopes) > 0 {
      +		db = db.executeScopes()
      +	}
      +
      +	var (
      +		curTime           = time.Now()
      +		stmt              = db.Statement
      +		resetBuildClauses bool
      +	)
      +
      +	// 注意这里的stmt.BuildClauses,后面会用到这个信息
      +	if len(stmt.BuildClauses) == 0 {
      +		stmt.BuildClauses = p.Clauses
      +		resetBuildClauses = true
      +	}
      +
      +	if optimizer, ok := db.Statement.Dest.(StatementModifier); ok {
      +		optimizer.ModifyStatement(stmt)
      +	}
      +
      +	// assign model values
      +	if stmt.Model == nil {
      +		stmt.Model = stmt.Dest
      +	} else if stmt.Dest == nil {
      +		stmt.Dest = stmt.Model
      +	}
      +
      +	// parse model values
      +	if stmt.Model != nil {
      +		if err := stmt.Parse(stmt.Model); err != nil && (!errors.Is(err, schema.ErrUnsupportedDataType) || (stmt.Table == "" && stmt.TableExpr == nil && stmt.SQL.Len() == 0)) {
      +			if errors.Is(err, schema.ErrUnsupportedDataType) && stmt.Table == "" && stmt.TableExpr == nil {
      +				db.AddError(fmt.Errorf("%w: Table not set, please set it like: db.Model(&user) or db.Table(\"users\")", err))
      +			} else {
      +				db.AddError(err)
      +			}
      +		}
      +	}
      +
      +	// assign stmt.ReflectValue
      +	if stmt.Dest != nil {
      +		stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
      +		for stmt.ReflectValue.Kind() == reflect.Ptr {
      +			if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
      +				stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
      +			}
      +
      +			stmt.ReflectValue = stmt.ReflectValue.Elem()
      +		}
      +		if !stmt.ReflectValue.IsValid() {
      +			db.AddError(ErrInvalidValue)
      +		}
      +	}
      +
      +	// 根据优先级执行不同callback的回调方法
      +	for _, f := range p.fns {
      +		f(db)
      +	}
      +
      +	if stmt.SQL.Len() > 0 {
      +		db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
      +			sql, vars := stmt.SQL.String(), stmt.Vars
      +			if filter, ok := db.Logger.(ParamsFilter); ok {
      +				sql, vars = filter.ParamsFilter(stmt.Context, stmt.SQL.String(), stmt.Vars...)
      +			}
      +			return db.Dialector.Explain(sql, vars...), db.RowsAffected
      +		}, db.Error)
      +	}
      +
      +	if !stmt.DB.DryRun {
      +		stmt.SQL.Reset()
      +		stmt.Vars = nil
      +	}
      +
      +	if resetBuildClauses {
      +		stmt.BuildClauses = nil
      +	}
      +
      +	return db
      +}
      +

      接下来,我们来看一下内置的callback是如何注册的。

      mysql.go

      var (
      +	// CreateClauses create clauses
      +	CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	// QueryClauses query clauses
      +	QueryClauses = []string{}
      +	// UpdateClauses update clauses
      +	UpdateClauses = []string{"UPDATE", "SET", "WHERE", "ORDER BY", "LIMIT"}
      +	// DeleteClauses delete clauses
      +	DeleteClauses = []string{"DELETE", "FROM", "WHERE", "ORDER BY", "LIMIT"}
      +
      +	defaultDatetimePrecision = 3
      +)
      +
      +...
      +
      +func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
      +	if dialector.DriverName == "" {
      +		dialector.DriverName = "mysql"
      +	}
      +
      +	if dialector.DefaultDatetimePrecision == nil {
      +		dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
      +	}
      +
      +	if dialector.Conn != nil {
      +		db.ConnPool = dialector.Conn
      +	} else {
      +		db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
      +		if err != nil {
      +			return err
      +		}
      +	}
      +
      +	withReturning := false
      +	if !dialector.Config.SkipInitializeWithVersion {
      +		err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
      +		if err != nil {
      +			return err
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "MariaDB") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportNullAsDefaultValue = true
      +			withReturning = checkVersion(dialector.ServerVersion, "10.5")
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.7.") {
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.") {
      +			dialector.Config.DisableDatetimePrecision = true
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "TiDB") {
      +			dialector.Config.DontSupportRenameColumnUnique = true
      +		}
      +	}
      +
      +	// register callbacks
      +	callbackConfig := &callbacks.Config{
      +		CreateClauses: CreateClauses,
      +		QueryClauses:  QueryClauses,
      +		UpdateClauses: UpdateClauses,
      +		DeleteClauses: DeleteClauses,
      +	}
      +
      +	if !dialector.Config.DisableWithReturning && withReturning {
      +		if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") {
      +			callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") {
      +			callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") {
      +			callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
      +		}
      +	}
      +
      +	// 注册默认callback
      +	callbacks.RegisterDefaultCallbacks(db, callbackConfig)
      +
      +	for k, v := range dialector.ClauseBuilders() {
      +		db.ClauseBuilders[k] = v
      +	}
      +	return
      +}
      +

      callbacks.go

      var (
      +	createClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	queryClauses  = []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"}
      +	updateClauses = []string{"UPDATE", "SET", "WHERE"}
      +	deleteClauses = []string{"DELETE", "FROM", "WHERE"}
      +)
      +
      +type Config struct {
      +	LastInsertIDReversed bool
      +	CreateClauses        []string
      +	QueryClauses         []string
      +	UpdateClauses        []string
      +	DeleteClauses        []string
      +}
      +
      +func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
      +	enableTransaction := func(db *gorm.DB) bool {
      +		return !db.SkipDefaultTransaction
      +	}
      +
      +	if len(config.CreateClauses) == 0 {
      +		config.CreateClauses = createClauses
      +	}
      +	if len(config.QueryClauses) == 0 {
      +		config.QueryClauses = queryClauses
      +	}
      +	if len(config.DeleteClauses) == 0 {
      +		config.DeleteClauses = deleteClauses
      +	}
      +	if len(config.UpdateClauses) == 0 {
      +		config.UpdateClauses = updateClauses
      +	}
      +
      +    // 注册不同类型的callback
      +	createCallback := db.Callback().Create()
      +	createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	createCallback.Register("gorm:before_create", BeforeCreate)
      +	createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
      +	createCallback.Register("gorm:create", Create(config))
      +	createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
      +	createCallback.Register("gorm:after_create", AfterCreate)
      +	createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	createCallback.Clauses = config.CreateClauses
      +
      +	queryCallback := db.Callback().Query()
      +	queryCallback.Register("gorm:query", Query)
      +	queryCallback.Register("gorm:preload", Preload)
      +	queryCallback.Register("gorm:after_query", AfterQuery)
      +	queryCallback.Clauses = config.QueryClauses
      +
      +	deleteCallback := db.Callback().Delete()
      +	deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	deleteCallback.Register("gorm:before_delete", BeforeDelete)
      +	deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
      +	deleteCallback.Register("gorm:delete", Delete(config))
      +	deleteCallback.Register("gorm:after_delete", AfterDelete)
      +	deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	deleteCallback.Clauses = config.DeleteClauses
      +
      +	updateCallback := db.Callback().Update()
      +	updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
      +	updateCallback.Register("gorm:before_update", BeforeUpdate)
      +	updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
      +	updateCallback.Register("gorm:update", Update(config))
      +	updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
      +	updateCallback.Register("gorm:after_update", AfterUpdate)
      +	updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	updateCallback.Clauses = config.UpdateClauses
      +
      +	rowCallback := db.Callback().Row()
      +	rowCallback.Register("gorm:row", RowQuery)
      +	rowCallback.Clauses = config.QueryClauses
      +
      +	rawCallback := db.Callback().Raw()
      +	rawCallback.Register("gorm:raw", RawExec)
      +	rawCallback.Clauses = config.QueryClauses
      +}
      +

      到这里,默认callback就注册完成了,但是是如何转成对应sql的呢?

      别急,我们继续往下看。

      RegisterDefaultCallbacks方法里注册了一个gorm:query类型的callback,对应的回调方法为Query

      query.go

      func Query(db *gorm.DB) {
      +	if db.Error == nil {
      +		// 调用BuildQuerySQL方法
      +		BuildQuerySQL(db)
      +
      +		if !db.DryRun && db.Error == nil {
      +			rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
      +			if err != nil {
      +				db.AddError(err)
      +				return
      +			}
      +			defer func() {
      +				db.AddError(rows.Close())
      +			}()
      +			gorm.Scan(rows, db, 0)
      +		}
      +	}
      +}
      +
      +func BuildQuerySQL(db *gorm.DB) {
      +	if db.Statement.Schema != nil {
      +		for _, c := range db.Statement.Schema.QueryClauses {
      +			db.Statement.AddClause(c)
      +		}
      +	}
      +
      +	if db.Statement.SQL.Len() == 0 {
      +		db.Statement.SQL.Grow(100)
      +		clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
      +
      +		if db.Statement.ReflectValue.Kind() == reflect.Struct && db.Statement.ReflectValue.Type() == db.Statement.Schema.ModelType {
      +			var conds []clause.Expression
      +			for _, primaryField := range db.Statement.Schema.PrimaryFields {
      +				if v, isZero := primaryField.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
      +					conds = append(conds, clause.Eq{Column: clause.Column{Table: db.Statement.Table, Name: primaryField.DBName}, Value: v})
      +				}
      +			}
      +
      +			if len(conds) > 0 {
      +				db.Statement.AddClause(clause.Where{Exprs: conds})
      +			}
      +		}
      +
      +		if len(db.Statement.Selects) > 0 {
      +			clauseSelect.Columns = make([]clause.Column, len(db.Statement.Selects))
      +			for idx, name := range db.Statement.Selects {
      +				if db.Statement.Schema == nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				} else if f := db.Statement.Schema.LookUpField(name); f != nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: f.DBName}
      +				} else {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && len(db.Statement.Omits) > 0 {
      +			selectColumns, _ := db.Statement.SelectAndOmitColumns(false, false)
      +			clauseSelect.Columns = make([]clause.Column, 0, len(db.Statement.Schema.DBNames))
      +			for _, dbName := range db.Statement.Schema.DBNames {
      +				if v, ok := selectColumns[dbName]; (ok && v) || !ok {
      +					clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{Table: db.Statement.Table, Name: dbName})
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && db.Statement.ReflectValue.IsValid() {
      +			queryFields := db.QueryFields
      +			if !queryFields {
      +				switch db.Statement.ReflectValue.Kind() {
      +				case reflect.Struct:
      +					queryFields = db.Statement.ReflectValue.Type() != db.Statement.Schema.ModelType
      +				case reflect.Slice:
      +					queryFields = db.Statement.ReflectValue.Type().Elem() != db.Statement.Schema.ModelType
      +				}
      +			}
      +
      +			if queryFields {
      +				stmt := gorm.Statement{DB: db}
      +				// smaller struct
      +				if err := stmt.Parse(db.Statement.Dest); err == nil && (db.QueryFields || stmt.Schema.ModelType != db.Statement.Schema.ModelType) {
      +					clauseSelect.Columns = make([]clause.Column, len(stmt.Schema.DBNames))
      +
      +					for idx, dbName := range stmt.Schema.DBNames {
      +						clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +					}
      +				}
      +			}
      +		}
      +
      +		// inline joins
      +		fromClause := clause.From{}
      +		if v, ok := db.Statement.Clauses["FROM"].Expression.(clause.From); ok {
      +			fromClause = v
      +		}
      +
      +		if len(db.Statement.Joins) != 0 || len(fromClause.Joins) != 0 {
      +			if len(db.Statement.Selects) == 0 && len(db.Statement.Omits) == 0 && db.Statement.Schema != nil {
      +				clauseSelect.Columns = make([]clause.Column, len(db.Statement.Schema.DBNames))
      +				for idx, dbName := range db.Statement.Schema.DBNames {
      +					clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +				}
      +			}
      +
      +			specifiedRelationsName := make(map[string]interface{})
      +			for _, join := range db.Statement.Joins {
      +				if db.Statement.Schema != nil {
      +					var isRelations bool // is relations or raw sql
      +					var relations []*schema.Relationship
      +					relation, ok := db.Statement.Schema.Relationships.Relations[join.Name]
      +					if ok {
      +						isRelations = true
      +						relations = append(relations, relation)
      +					} else {
      +						// handle nested join like "Manager.Company"
      +						nestedJoinNames := strings.Split(join.Name, ".")
      +						if len(nestedJoinNames) > 1 {
      +							isNestedJoin := true
      +							gussNestedRelations := make([]*schema.Relationship, 0, len(nestedJoinNames))
      +							currentRelations := db.Statement.Schema.Relationships.Relations
      +							for _, relname := range nestedJoinNames {
      +								// incomplete match, only treated as raw sql
      +								if relation, ok = currentRelations[relname]; ok {
      +									gussNestedRelations = append(gussNestedRelations, relation)
      +									currentRelations = relation.FieldSchema.Relationships.Relations
      +								} else {
      +									isNestedJoin = false
      +									break
      +								}
      +							}
      +
      +							if isNestedJoin {
      +								isRelations = true
      +								relations = gussNestedRelations
      +							}
      +						}
      +					}
      +
      +					if isRelations {
      +						genJoinClause := func(joinType clause.JoinType, parentTableName string, relation *schema.Relationship) clause.Join {
      +							tableAliasName := relation.Name
      +							if parentTableName != clause.CurrentTable {
      +								tableAliasName = utils.NestedRelationName(parentTableName, tableAliasName)
      +							}
      +
      +							columnStmt := gorm.Statement{
      +								Table: tableAliasName, DB: db, Schema: relation.FieldSchema,
      +								Selects: join.Selects, Omits: join.Omits,
      +							}
      +
      +							selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
      +							for _, s := range relation.FieldSchema.DBNames {
      +								if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
      +									clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
      +										Table: tableAliasName,
      +										Name:  s,
      +										Alias: utils.NestedRelationName(tableAliasName, s),
      +									})
      +								}
      +							}
      +
      +							exprs := make([]clause.Expression, len(relation.References))
      +							for idx, ref := range relation.References {
      +								if ref.OwnPrimaryKey {
      +									exprs[idx] = clause.Eq{
      +										Column: clause.Column{Table: parentTableName, Name: ref.PrimaryKey.DBName},
      +										Value:  clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +									}
      +								} else {
      +									if ref.PrimaryValue == "" {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: parentTableName, Name: ref.ForeignKey.DBName},
      +											Value:  clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName},
      +										}
      +									} else {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +											Value:  ref.PrimaryValue,
      +										}
      +									}
      +								}
      +							}
      +
      +							{
      +								onStmt := gorm.Statement{Table: tableAliasName, DB: db, Clauses: map[string]clause.Clause{}}
      +								for _, c := range relation.FieldSchema.QueryClauses {
      +									onStmt.AddClause(c)
      +								}
      +
      +								if join.On != nil {
      +									onStmt.AddClause(join.On)
      +								}
      +
      +								if cs, ok := onStmt.Clauses["WHERE"]; ok {
      +									if where, ok := cs.Expression.(clause.Where); ok {
      +										where.Build(&onStmt)
      +
      +										if onSQL := onStmt.SQL.String(); onSQL != "" {
      +											vars := onStmt.Vars
      +											for idx, v := range vars {
      +												bindvar := strings.Builder{}
      +												onStmt.Vars = vars[0 : idx+1]
      +												db.Dialector.BindVarTo(&bindvar, &onStmt, v)
      +												onSQL = strings.Replace(onSQL, bindvar.String(), "?", 1)
      +											}
      +
      +											exprs = append(exprs, clause.Expr{SQL: onSQL, Vars: vars})
      +										}
      +									}
      +								}
      +							}
      +
      +							return clause.Join{
      +								Type:  joinType,
      +								Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName},
      +								ON:    clause.Where{Exprs: exprs},
      +							}
      +						}
      +
      +						parentTableName := clause.CurrentTable
      +						for _, rel := range relations {
      +							// joins table alias like "Manager, Company, Manager__Company"
      +							nestedAlias := utils.NestedRelationName(parentTableName, rel.Name)
      +							if _, ok := specifiedRelationsName[nestedAlias]; !ok {
      +								fromClause.Joins = append(fromClause.Joins, genJoinClause(join.JoinType, parentTableName, rel))
      +								specifiedRelationsName[nestedAlias] = nil
      +							}
      +
      +							if parentTableName != clause.CurrentTable {
      +								parentTableName = utils.NestedRelationName(parentTableName, rel.Name)
      +							} else {
      +								parentTableName = rel.Name
      +							}
      +						}
      +					} else {
      +						fromClause.Joins = append(fromClause.Joins, clause.Join{
      +							Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +						})
      +					}
      +				} else {
      +					fromClause.Joins = append(fromClause.Joins, clause.Join{
      +						Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +					})
      +				}
      +			}
      +
      +			db.Statement.AddClause(fromClause)
      +		} else {
      +			db.Statement.AddClauseIfNotExists(clause.From{})
      +		}
      +
      +		db.Statement.AddClauseIfNotExists(clauseSelect)
      +
      +		// db.Statement.BuildClauses眼熟吗?还记得前面的stmt.BuildClauses吗
      +		db.Statement.Build(db.Statement.BuildClauses...)
      +	}
      +}
      +

      重头戏终于来了,Query方法里调用了BuildQuerySQl,看名字也能猜到这里就是生成sql了,这里最终调用了db.Statement.Build方法。

      statement.go

      // Build build sql with clauses names
      +func (stmt *Statement) Build(clauses ...string) {
      +	var firstClauseWritten bool
      +
      +	for _, name := range clauses {
      +		if c, ok := stmt.Clauses[name]; ok {
      +			if firstClauseWritten {
      +				stmt.WriteByte(' ')
      +			}
      +
      +			firstClauseWritten = true
      +			if b, ok := stmt.DB.ClauseBuilders[name]; ok {
      +				b(c, stmt)
      +			} else {
      +				c.Build(stmt)
      +			}
      +		}
      +	}
      +}
      +

      这里会根据statementBuildCluauses属性,执行ClauseBuild方法。

      clause.go

      // ClauseBuilder clause builder, allows to customize how to build clause
      +type ClauseBuilder func(Clause, Builder)
      +
      +type Writer interface {
      +	WriteByte(byte) error
      +	WriteString(string) (int, error)
      +}
      +
      +// Builder builder interface
      +type Builder interface {
      +	Writer
      +	WriteQuoted(field interface{})
      +	AddVar(Writer, ...interface{})
      +	AddError(error) error
      +}
      +
      +// Clause
      +type Clause struct {
      +	Name                string // WHERE
      +	BeforeExpression    Expression
      +	AfterNameExpression Expression
      +	AfterExpression     Expression
      +	Expression          Expression
      +	Builder             ClauseBuilder
      +}
      +
      +// Build build clause
      +func (c Clause) Build(builder Builder) {
      +	if c.Builder != nil {
      +		c.Builder(c, builder)
      +	} else if c.Expression != nil {
      +		if c.BeforeExpression != nil {
      +			c.BeforeExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.Name != "" {
      +			builder.WriteString(c.Name)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.AfterNameExpression != nil {
      +			c.AfterNameExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		c.Expression.Build(builder)
      +
      +		if c.AfterExpression != nil {
      +			builder.WriteByte(' ')
      +			c.AfterExpression.Build(builder)
      +		}
      +	}
      +}
      +

      这里会执行对应Clause的Build方法。

      // Select select attrs when querying, updating, creating
      +type Select struct {
      +	Distinct   bool
      +	Columns    []Column
      +	Expression Expression
      +}
      +
      +func (s Select) Name() string {
      +	return "SELECT"
      +}
      +
      +func (s Select) Build(builder Builder) {
      +	if len(s.Columns) > 0 {
      +		if s.Distinct {
      +			builder.WriteString("DISTINCT ")
      +		}
      +
      +		for idx, column := range s.Columns {
      +			if idx > 0 {
      +				builder.WriteByte(',')
      +			}
      +			builder.WriteQuoted(column)
      +		}
      +	} else {
      +		builder.WriteByte('*')
      +	}
      +}
      +
      +func (s Select) MergeClause(clause *Clause) {
      +	if s.Expression != nil {
      +		if s.Distinct {
      +			if expr, ok := s.Expression.(Expr); ok {
      +				expr.SQL = "DISTINCT " + expr.SQL
      +				clause.Expression = expr
      +				return
      +			}
      +		}
      +
      +		clause.Expression = s.Expression
      +	} else {
      +		clause.Expression = s
      +	}
      +}
      +

      这是Select类型的Clause定义,是不是一下就清楚了。

      gorm通过callback里注册Clause,在Clause里实现了sql拼接操作。

      看了几回源码,这次总算是搞清楚了。

      💬评论
      \ No newline at end of file diff --git a/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/index.html b/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/index.html new file mode 100644 index 000000000..018dc26dd --- /dev/null +++ b/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/index.html @@ -0,0 +1,18 @@ +检测网站支持的SSL/TLS协议版本 | 流动 +

      检测网站支持的SSL/TLS协议版本

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。

      为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。

      1. 检测是否支持TLSv1

        openssl s_client -connect [ip or 域名]:443 -tls1
        +
      2. 检测是否支持TLSv1.1

        openssl s_client -connect [ip or 域名]:443 -tls1_1
        +
      3. 检测是否支持TLSv1.2

        openssl s_client -connect [ip or 域名]:443 -tls1_2
        +

      参考资料:How to check what SSL/TLS versions are available for a website?

      💬评论
      \ No newline at end of file diff --git a/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f.jpg b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f.jpg new file mode 100644 index 000000000..7c40062f3 Binary files /dev/null and b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f.jpg differ diff --git a/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu1587438054435300824.webp b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu1587438054435300824.webp new file mode 100644 index 000000000..d4b8ae6f7 Binary files /dev/null and b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu1587438054435300824.webp differ diff --git a/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu9186879431374056423.jpg b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu9186879431374056423.jpg new file mode 100644 index 000000000..1820412b6 Binary files /dev/null and b/posts/how-to-modify-marital-status/VYnKduoHTtzDw4f_hu9186879431374056423.jpg differ diff --git a/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN.jpg b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN.jpg new file mode 100644 index 000000000..c0a026882 Binary files /dev/null and b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN.jpg differ diff --git a/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu13286695534202220428.jpg b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu13286695534202220428.jpg new file mode 100644 index 000000000..f1034cab4 Binary files /dev/null and b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu13286695534202220428.jpg differ diff --git a/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu9138077917932714622.webp b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu9138077917932714622.webp new file mode 100644 index 000000000..9faa5cfa8 Binary files /dev/null and b/posts/how-to-modify-marital-status/WpV28uBMvjyzPJN_hu9138077917932714622.webp differ diff --git a/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2.jpg b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2.jpg new file mode 100644 index 000000000..8ff435eab Binary files /dev/null and b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2.jpg differ diff --git a/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu15506952337382643592.jpg b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu15506952337382643592.jpg new file mode 100644 index 000000000..8fa75d033 Binary files /dev/null and b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu15506952337382643592.jpg differ diff --git a/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu6904186450706354453.webp b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu6904186450706354453.webp new file mode 100644 index 000000000..93028d465 Binary files /dev/null and b/posts/how-to-modify-marital-status/f7HvbKlitaOm1T2_hu6904186450706354453.webp differ diff --git a/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n.jpg b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n.jpg new file mode 100644 index 000000000..3bb2dacba Binary files /dev/null and b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n.jpg differ diff --git a/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu5478875653684974159.webp b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu5478875653684974159.webp new file mode 100644 index 000000000..3f0981468 Binary files /dev/null and b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu5478875653684974159.webp differ diff --git a/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu9672467465638887422.jpg b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu9672467465638887422.jpg new file mode 100644 index 000000000..a28176f7e Binary files /dev/null and b/posts/how-to-modify-marital-status/fohyvH3iGaTtm1n_hu9672467465638887422.jpg differ diff --git a/posts/how-to-modify-marital-status/index.html b/posts/how-to-modify-marital-status/index.html new file mode 100644 index 000000000..2040efd6d --- /dev/null +++ b/posts/how-to-modify-marital-status/index.html @@ -0,0 +1,15 @@ +如何在北京公积金网站上修改婚姻状况 | 流动 +

      如何在北京公积金网站上修改婚姻状况

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告

      时间:2020年01月08日

      来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html

      1月8日,北京公积金中心发文,从1月10日开始可以网上办理公积金提取了。

      这里单独讲一下外地领证的情况下,如何修改婚姻状况。

      1. 进入提取页面,默认显示为未说明的婚姻状况。

        QQ截图20200117170836.jpg

      2. 点击婚姻状况,选择已婚。

        QQ截图20200117170917.jpg

        可以看到婚姻状态相关的输入框都为灰色,不可修改。

      3. 点击婚姻信息修改按钮,会弹出一个民政校验的弹窗,因为我是外地领证,这里查不到信息。

        QQ截图20200117171149.jpg

        注意图片右下角还是只有一个婚姻信息修改按钮。

      4. 点击弹窗里的确认按钮。

        QQ截图20200117171236.jpg

        这下婚姻状况相关的输入框都可以填写了。

        另外图片右下角里多了一个上传结婚证按钮。

      5. 填写完信息后,点击上传结婚证按钮。

        QQ截图20200117171252.jpg

      6. 按说明上传两张结婚证照片,点击确认即可。

      7. 其余的按公积金官网文档操作,最后提交即可。

      关于提取时间,我是前一天中午申请,第二天下午就到账了,效率还是很棒的。

      💬评论
      \ No newline at end of file diff --git a/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI.jpg b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI.jpg new file mode 100644 index 000000000..bcce39236 Binary files /dev/null and b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI.jpg differ diff --git a/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu17392966183303399434.jpg b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu17392966183303399434.jpg new file mode 100644 index 000000000..4f4a90d19 Binary files /dev/null and b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu17392966183303399434.jpg differ diff --git a/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu999288024124128959.webp b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu999288024124128959.webp new file mode 100644 index 000000000..855c731e3 Binary files /dev/null and b/posts/how-to-modify-marital-status/kMhnwRzgtrNOFAI_hu999288024124128959.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906.png new file mode 100644 index 000000000..7dea1f1f9 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu13499488781587240421.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu13499488781587240421.png new file mode 100644 index 000000000..e6038fdb7 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu13499488781587240421.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu3640116897298145221.webp b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu3640116897298145221.webp new file mode 100644 index 000000000..442f2113e Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027193906_hu3640116897298145221.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257.png new file mode 100644 index 000000000..c84fd3a72 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu10507043871738030836.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu10507043871738030836.png new file mode 100644 index 000000000..768c84f54 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu10507043871738030836.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu14161076425847383800.webp b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu14161076425847383800.webp new file mode 100644 index 000000000..21e83ad61 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194257_hu14161076425847383800.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753.png new file mode 100644 index 000000000..8a63ec014 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu112325605454886179.webp b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu112325605454886179.webp new file mode 100644 index 000000000..57fcf0a3a Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu112325605454886179.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu1820397607132649156.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu1820397607132649156.png new file mode 100644 index 000000000..19ef09a8e Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027194753_hu1820397607132649156.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238.png new file mode 100644 index 000000000..ae1699db2 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu14301052623136109094.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu14301052623136109094.png new file mode 100644 index 000000000..5c2f20b50 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu14301052623136109094.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu1630441103893198177.webp b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu1630441103893198177.webp new file mode 100644 index 000000000..73a337b13 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/20231027200238_hu1630441103893198177.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031.png new file mode 100644 index 000000000..892b88580 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu15241845893383975374.png b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu15241845893383975374.png new file mode 100644 index 000000000..5344d8223 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu15241845893383975374.png differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu16459305612703530959.webp b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu16459305612703530959.webp new file mode 100644 index 000000000..b8ae54fe8 Binary files /dev/null and b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/202310280923031_hu16459305612703530959.webp differ diff --git a/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/index.html b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/index.html new file mode 100644 index 000000000..40eddf892 --- /dev/null +++ b/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/index.html @@ -0,0 +1,26 @@ +使用Google Indexing API加速博客收录 | 流动 +

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      操作步骤:

      1. 申请Google API凭证

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        点击后面的三个点按钮,选择管理密钥。

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

      2. 将服务账号关联到Google Search Console

        进入Google Search Console控制台,选择你的网站。

        找到设置里的用户和权限,点击添加用户。

        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      3. 配置Github Action

        • 添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

        • 编辑workflow编排任务,新增步骤

        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

      💬评论
      \ No newline at end of file diff --git a/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R.png b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R.png new file mode 100644 index 000000000..c2d6f2c3d Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R.png differ diff --git a/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu297373841140023210.jpg b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu297373841140023210.jpg new file mode 100644 index 000000000..ca8d33052 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu297373841140023210.jpg differ diff --git a/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu3578150447160391584.webp b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu3578150447160391584.webp new file mode 100644 index 000000000..0b46f6464 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu3578150447160391584.webp differ diff --git a/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu7649500071980006805.webp b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu7649500071980006805.webp new file mode 100644 index 000000000..f4c7324fd Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu7649500071980006805.webp differ diff --git a/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu9951478377709350008.jpg b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu9951478377709350008.jpg new file mode 100644 index 000000000..0b6cc9a99 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/74TRx6aETydsBGa2IZ7R_hu9951478377709350008.jpg differ diff --git a/posts/hugo-auto-generate-image-width-and-height/index.html b/posts/hugo-auto-generate-image-width-and-height/index.html new file mode 100644 index 000000000..48aab86a7 --- /dev/null +++ b/posts/hugo-auto-generate-image-width-and-height/index.html @@ -0,0 +1,97 @@ +累计布局偏移修复方案改进 —— 自动生成图片宽高 | 流动 +

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      遗留的问题

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      手动输入每张图片的宽高

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      思路

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      要插入的图片代码是类似这样的:

      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      picgo config

      此路不通,只好再想新的办法了。

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      解决方案

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      1. 新增css配置

        新增如下配置,否则会导致图片变形。

        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. 添加render-image.html文件

        代码如下:

        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      3. 添加cover.html文件

        代码如下:

        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      💬评论
      \ No newline at end of file diff --git a/posts/hugo-auto-generate-image-width-and-height/picgo-config.png b/posts/hugo-auto-generate-image-width-and-height/picgo-config.png new file mode 100644 index 000000000..6f2bceac3 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/picgo-config.png differ diff --git a/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu7723921126585497891.png b/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu7723921126585497891.png new file mode 100644 index 000000000..c0469a084 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu7723921126585497891.png differ diff --git a/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu8143335939496747907.webp b/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu8143335939496747907.webp new file mode 100644 index 000000000..e2df98194 Binary files /dev/null and b/posts/hugo-auto-generate-image-width-and-height/picgo-config_hu8143335939496747907.webp differ diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 000000000..ab49dae51 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,62 @@ +文章 | 流动 +

      Github Pages 部署流程解析

      上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。 +看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。 +- name: Install dependencies run: pnpm install - name: Build run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload dist repository path: './dist' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 核心逻辑就是上面这段。 +...

      2024-09-24 · 2 min · 570 words · Liudon

      搭建个人锻炼页面

      工作的缘故,平时基本一坐一天,缺少运动。 +时间久了,各种毛病也就出来了。 +搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。 +坚持了一段时间,也不了了之了。 +...

      2024-09-22 · 1 min · 325 words · Liudon

      你好 Follow

      Follow: Next generation information browser. +最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。 +蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。 +...

      2024-09-17 · 1 min · 271 words · Liudon

      中秋爬山

      中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。 +晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。 +...

      2024-09-16 · 2 min · 704 words · Liudon

      Google Adsense的审核之旅

      中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。 +偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。 +...

      2024-09-16 · 1 min · 411 words · Liudon

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。 +背景 周末更新博客时,发现workflow的上传IPFS任务执行失败了。 +...

      2024-09-04 · 3 min · 1246 words · Liudon

      一次简短的青岛之行

      刚放暑假的时候,就答应了娃带她去一趟青岛。 +8月份要回老家,所以定在了7月中下旬出发。 +车票/酒店都订好了,结果来了个台风格美。 +出发前一周一直在查天气,就怕去了一直下雨。 +...

      2024-08-31 · 2 min · 998 words · Liudon

      解决 "undeclared name: any (requires version go1.18 or later)" 编译错误

      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ $ protoc-gen-go --version protoc-gen-go v1.34.2 $ $ sh make.sh user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) $ 流水线编译报错,其中make.sh文件代码: +... protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto ... go build 同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。 +...

      2024-06-14 · 1 min · 473 words · Liudon

      搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

      背景 4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。 +没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。 +...

      2024-05-22 · 3 min · 1262 words · Liudon

      302跳转的跨域问题(CORS)

      302跳转的跨域问题 +场景一:302不返回跨域头 请求 +GET /302 HTTP/1.1 Host: liudon.xyz Origin: https://www.baidu.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 返回 +HTTP/1.1 200 OK Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Cf-Ray: 88535773eaf5107e-HKG Content-Length: 143 Content-Type: text/html Date: Fri, 17 May 2024 11:42:00 GMT Expires: Thu, 01 Jan 1970 00:00:01 GMT Location: https://liudon.org Server: cloudflare Vary: Accept-Encoding 浏览器报错 +...

      2024-05-17 · 2 min · 721 words · Liudon

      GORM增加sqlcommenter特性

      什么是sqlcommenter? +sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side. +...

      2024-04-18 · 2 min · 742 words · Liudon

      源码分析:GORM是如何生成sql的

      在gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。 +gorm使用示例 +package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) type Product struct { gorm.Model Code string Price uint } func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) var product Product db.First(&product, 1) // 根据整型主键查找 } 我们以First查询为例,看一下是怎么转成具体sql的。 +...

      2024-04-18 · 6 min · 2737 words · Liudon

      工银亚洲网银密码重置

      18年的时候办了张工银亚洲的银行卡,好几年没有用过了。 +今年想起来了,发现网银登不上了,密码忘了。 +最悲剧的是,试了超过10次,账户冻结了。 +打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。 +...

      2024-03-16 · 1 min · 481 words · Liudon

      加速Cloudflare访问

      背景 这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。 +众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。 +...

      2024-02-21 · 3 min · 1128 words · Liudon

      2023年终总结

      2023年过完了,是时候来个总结了。 +博客 2023年一共更新了15篇内容,共计12000字。 +访问Top3的文章: +...

      2024-01-04 · 2 min · 748 words · Liudon

      2023年12月北京暴雪记录

      记录暴雪下普通打工人的生活。 +12月14日 周四 北京的雪已经连着下了两天了。 +12月11日,也是因为下雪,晚上打车打到10点半才叫到车。 +所以这次下雪后,晚上就早走了。 +...

      2023-12-16 · 2 min · 599 words · Liudon

      使用Hugo实现响应式和优化的图片

      继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。 +问题 在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高。 +经过一段运行后,发现这里有一个弊端。 +Run hugo --gc --minify --cleanDestinationDir Start building sites … hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer 随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。 +...

      2023-12-10 · 5 min · 2021 words · Liudon

      加速Google Analytics

      起因 Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。 +最近在优化页面访问速度,发现Google Analytics是一个优化点。 +优化 1. 访问加速 国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。 +...

      2023-12-02 · 2 min · 870 words · Liudon

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。 +今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。 +...

      2023-10-27 · 2 min · 635 words · Liudon

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon
      \ No newline at end of file diff --git a/posts/index.xml b/posts/index.xml new file mode 100644 index 000000000..bc5e0d17d --- /dev/null +++ b/posts/index.xml @@ -0,0 +1,8929 @@ + + + + 文章 on 流动 + https://liudon.com/posts/ + Recent content in 文章 on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 24 Sep 2024 21:30:21 +0800 + + + Github Pages 部署流程解析 + https://liudon.com/posts/github-pages-deployment-tutorial/ + Tue, 24 Sep 2024 21:30:21 +0800 + https://liudon.com/posts/github-pages-deployment-tutorial/ + <p>上周末在<a href="https://liudon.com/posts/building-a-workout-page/">搭建个人锻炼页面</a>时,遇到个<code>Github Pages</code>部署的困惑。</p> +<p>看了<code>running_page</code>项目文档,是支持部署到<code>Github Pages</code>页面的,对应的操作流程定义在<a href="https://github.com/yihong0618/running_page/blob/master/.github/workflows/gh-pages.yml">github/workflows/gh-pages.yml</a>文件。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Install dependencies +</span></span><span style="display:flex;"><span> run: pnpm install +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Build +</span></span><span style="display:flex;"><span> run: PATH_PREFIX<span style="color:#f92672">=/$</span>{{ github<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>repository<span style="color:#f92672">.</span>name }} pnpm build +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Upload artifact +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>upload<span style="color:#f92672">-</span>pages<span style="color:#f92672">-</span>artifact<span style="color:#960050;background-color:#1e0010">@</span>v3 +</span></span><span style="display:flex;"><span> with: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Upload dist repository</span> +</span></span><span style="display:flex;"><span> path: <span style="color:#e6db74">&#39;./dist&#39;</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Deploy to GitHub Pages +</span></span><span style="display:flex;"><span> id: deployment +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>deploy<span style="color:#f92672">-</span>pages<span style="color:#960050;background-color:#1e0010">@</span>v4 +</span></span></code></pre></div><p>核心逻辑就是上面这段。</p> + 上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。

      +

      看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。

      +
          - name: Install dependencies
      +    run: pnpm install
      +    - name: Build
      +    run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build
      +
      +    - name: Upload artifact
      +    uses: actions/upload-pages-artifact@v3
      +    with:
      +        # Upload dist repository
      +        path: './dist'
      +    - name: Deploy to GitHub Pages
      +    id: deployment
      +    uses: actions/deploy-pages@v4
      +

      核心逻辑就是上面这段。

      +

      之前搞过部署hugo静态站点到Github Pages,使用的分支方式部署,编译后的静态文件单独用一个分支存放。

      +

      这里以我自己的博客项目举例,大致流程如下图:

      +

      + +github-pages-deploy-flow + + +

      +

      按我的理解,这里最终访问的文件内容是存在gh-page分支下的。

      +

      但是实际部署完running_page项目后,我发现并没有出现gh-page分支,但是Github Pages却可以正常访问。

      +

      有点不可思议,这个访问的数据是在哪里的呢?

      +

      带着这个疑问,在v2ex上发了个咨询贴

      +

      经过网友解惑,大致搞明白了这里的流程:

      +

      + +github-pages-deploy-flow + + +

      +

      Github Pages的发布源有两种方式,通过分支部署和通过Github Actions部署,分别对应上图的两条分支。

      +

      这里最终都会将build后的静态文件部署到Github Pages服务上,供用户访问。

      +

      分支部署的方式,其实是有一个内置工作流部署到Github Pages服务上的。

      +

      + + + + +

      +

      整个部署流程大致就是这样,最终的静态文件都是存在Github Pages服务上的。

      +]]>
      +
      + + 搭建个人锻炼页面 + https://liudon.com/posts/building-a-workout-page/ + Sun, 22 Sep 2024 16:57:38 +0800 + https://liudon.com/posts/building-a-workout-page/ + <p>工作的缘故,平时基本一坐一天,缺少运动。</p> +<p>时间久了,各种毛病也就出来了。</p> +<p>搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。</p> +<p>坚持了一段时间,也不了了之了。</p> + 工作的缘故,平时基本一坐一天,缺少运动。

      +

      时间久了,各种毛病也就出来了。

      +

      搬到新大楼后,每天中午吃完饭楼下遛个弯,走一走,身体精神也好了很多。

      +

      坚持了一段时间,也不了了之了。

      +

      今年开始,决定骑车通勤,上下班路上运动一下。

      +

      最近在别人博客里发现了运动记录,发现是通过running_page项目实现的。

      +

      顺藤摸瓜,又发现了workouts_page项目,支持多种运动。

      +

      于是看文档,部署起来,我的个人锻炼页面

      +

      + +workout page + + +

      +

      整个流程:

      +

      使用Apple Watch记录运动,导入到Strava应用里,在通过workouts_page工作流拉取数据构建页面。

      +

      部署过程中,顺带发现个问题,提了个PR。

      +

      这里还有个小插曲,没搞明白PR的流程,在未合入前又提交了其他代码,只好重新提了一个PR。😂

      +]]>
      +
      + + 你好 Follow + https://liudon.com/posts/hi-follow/ + Tue, 17 Sep 2024 00:53:38 +0800 + https://liudon.com/posts/hi-follow/ + <blockquote> +<p>Follow: Next generation information browser.</p> +</blockquote> +<p>最近博客圈开始流行<code>Follow邀请码</code>,大家各种求码,一码难求。</p> +<p>蹲在<code>Discord</code>群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。</p> + +

      Follow: Next generation information browser.

      + +

      最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。

      +

      蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。

      +

      上周五好不容易加上管理员,要到了一枚邀请码,终于可以激活体验了。

      +

      Follow里,订阅变的异常简单,输入url,它会自己检查rss订阅。

      +

      + +Follow + + +

      +

      另外发现我的博客,在Follow显示的内容不全。

      +

      检查了一下,发现是输出的RSS内容不全。

      +

      修改hugo配置文件,开启RSS输出全文。

      +
      ShowFullTextinRSS: true
      +

      从木木大佬那里看到,可以认证自己的Feed,我也来搞一下我的。

      +
      This message is used to verify that this feed (feedId:55815884011044914) belongs to me (userId:56204227179125760). 
      +Join me in enjoying the next generation information browser https://follow.is.
      +

      咱也是带标的了。

      +

      + +认证 + + +

      +]]>
      +
      + + 中秋爬山 + https://liudon.com/posts/mid-autumn-festival-climb/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/posts/mid-autumn-festival-climb/ + <p>中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。</p> +<p>晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。</p> + 中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。

      +

      晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。

      +

      园博园开了灯会,微博上看说是人巨多,还是放弃吧。

      +

      跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。

      +

      第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。

      +

      还好不远,山也不高,我们也不着急,就当遛弯。

      +

      来了好几次了,进园就直奔主题:爬。

      +

      这次我俩先走了一段山路,虽然是台阶,但是确实快。

      +

      昨天玩的太累,我们商量着还是继续走坡道吧。

      +

      花了40分钟左右登顶,最快的一次记录了。

      +

      + +登顶 + + +

      +

      爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。

      +

      + +小憩 + + +

      +

      今天天气一般,能见度不高,远处都是灰蒙蒙的。

      +

      + + + + +

      +

      歇到1点多,我俩开始下山。

      +

      之前在微博看到陈晓卿分享的一家新疆馆子白钻美食,决定晚上带娃去尝尝。

      +

      坐了1个半小时的地铁,到了吕营大街。

      +

      高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。

      +

      我俩傻乎乎的爬楼上来,累个够呛。

      +

      建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。

      +

      4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。

      +

      馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂

      +

      不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。

      +

      + +羊腿抓饭 + + +

      +

      + +羊肉串 + + +

      +

      然后又点了一份过油拌面,面条非常劲道,味道非常棒。

      +

      + +过油拌面 + + +

      +

      喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。

      +

      + +砖茶 + + +

      +

      店不大,但是味道挺好,推荐去试试,就是有点远。

      +

      + +白钻美食 + + +

      +

      吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。

      +

      今天是暴走的一天。

      +

      + +健身记录 + + +

      +]]>
      +
      + + Google Adsense的审核之旅 + https://liudon.com/posts/my-google-adsense-approval-journey/ + Mon, 16 Sep 2024 23:18:50 +0800 + https://liudon.com/posts/my-google-adsense-approval-journey/ + <p>中午的时候,突然收到一条消息,打开一看,提示我的<code>Google Adsense</code>审核通过了。</p> +<p>偶然发现<code>Google Adsense</code>里居然有40美金,想起来是之前<a href="https://liudon.org">老博客</a>加的广告。</p> + 中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。

      +

      偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。

      +

      看着新博客每天也有了一些访问,打算申请Google Adsense,补充些维护成本。

      +

      按之前的流程搞了一遍,提交了申请。

      +

      结果过了1周多,收到审核不通过邮件,说是不符合规范:低质内容,质量不高。

      +

      搜了一下,说是现在新网站审核门槛高了。

      +

      不放弃,继续申请呗。

      +

      + +approval google adsense + + +

      +

      从3月份开始,申请了7次,全部被拒。

      +

      尤其是8月25日被拒后,提示我审核次数过多,必须得等到8月31日以后才能再次申请。

      +

      上社区发了帖子,咨询到底是什么原因,结果也没收到答复。

      +

      这个时候,就已经有点心灰意冷,想要放弃了。

      +

      9月5日的时候,想着再最后申请一把试试看,再不通过就算了。

      +

      等了1周多,感觉这次估计又悬了,已经放弃了,结果今天竟然审核通过了。

      +

      历经了8次申请,耗时半年,终于申请下来了,算是这段时间难得的一件好事。

      +]]>
      +
      + + 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + <p>本文会介绍如何接入<code>filebase</code>的Names(IPNS)服务,使你的<code>IPFS</code>站点持久在线。</p> +<h4 id="背景">背景</h4> +<p>周末更新博客时,发现workflow的上传IPFS任务执行失败了。</p> + 本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      +

      背景

      +

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      +
      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      +
      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      +

      Github文档,官方已经不再更新了。

      +
      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      +

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      +

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      +

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      +

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      +

      折腾记录

      +

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      +

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      +
      生成密钥
      +

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      +

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      +

      所以需要将云主机的密钥导出后,导入到filebase

      +

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      +
      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      +

      + +chat + + +

      +

      查看已有密钥:

      +
      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      +
      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      +

      filebase导入key要求为base64编码,将其转为base64编码:

      +
      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      +
      创建NAME
      +

      进入filebase控制台,点击Create Name

      +

      + +input + + +

      +
      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      +
      修改workflow
      +
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      +

      提交后,执行workflow,在执行结果里找到IPNS地址。

      +
      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +
      +

      更新域名的dnslink值:

      +

      普通域名

      +

      + +dns + + +

      +

      eth域名

      +

      + +eth + + +

      +

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      +

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      +]]>
      +
      + + 一次简短的青岛之行 + https://liudon.com/posts/the-trip-of-qingdao/ + Sat, 31 Aug 2024 21:24:53 +0800 + https://liudon.com/posts/the-trip-of-qingdao/ + <p>刚放暑假的时候,就答应了娃带她去一趟青岛。</p> +<p>8月份要回老家,所以定在了7月中下旬出发。</p> +<p>车票/酒店都订好了,结果来了个台风格美。</p> +<p>出发前一周一直在查天气,就怕去了一直下雨。</p> + 刚放暑假的时候,就答应了娃带她去一趟青岛。

      +

      8月份要回老家,所以定在了7月中下旬出发。

      +

      车票/酒店都订好了,结果来了个台风格美。

      +

      出发前一周一直在查天气,就怕去了一直下雨。

      +

      看了台风的预测路径,感觉可能能赶在台风来之前的空档,硬着头皮出发吧。

      +

      7月26日乘坐高铁G203,中午12点左右到达青岛。

      +

      老天很给面子,是个晴天,还有点晒。

      +

      + +出行计划 + + +

      +

      本来的计划路线:

      +
      第一天:
      +
      +中午到达青岛 -> 天主教堂 -> 栈桥 -> 八大关 -> 第二海水浴场
      +
      +第二天:
      +
      +海底世界/极地海洋馆 -> 石老人海水浴场 -> 五四广场/奥帆中心夜景
      +
      +第三天:
      +
      +酒店休息返京
      +

      按这个路线,定了两个酒店,一个在栈桥附近,一个在海洋馆附近。

      +

      到了青岛后,先去酒店放行李,然后打车去吃饭。

      +

      青岛的第一顿饭,我们选了吃海鲜,事前查了些攻略,选择了栈桥附近的燕欣饭馆。

      +

      + +燕欣饭馆 + + +

      +

      中午很饿,上来就吃,忘记拍照了,只有吃完后的照片了。

      +

      + +一扫而光 + + +

      +

      油焖大虾相当不错,海肠捞饭非常好吃,韭菜很鲜。

      +

      3个人,一共花了240元,非常推荐的一家店。

      +

      吃完饭,溜达到天主教堂打卡。

      +

      + +天主教堂 + + +

      +

      然后是栈桥,人非常多,中午非常晒。

      +

      + +栈桥 + + +

      +

      于是回酒店稍作休息,决定打车直奔第二海水浴场玩水。

      +

      踩水的感觉太好玩了,娃从一开始的有点害怕,到后面追着水玩。

      +

      + +踩水 + + +

      +

      玩完打车去的美团推荐的双合园,地方很小,需要等位。

      +

      吃下来,感觉不如第一顿好吃,菜品一般,不太推荐。

      +

      吃完已经9点了,错过了夜景时间,直接回酒店休息了。

      +

      第二天起床,发现外面下雨了,风很大,最终没逃过台风的影响。

      +

      昨天路过海洋馆,看了外面排队的人巨多,决定不去室内这种海洋馆了。

      +

      先去了银鱼巷溜达一圈,没啥看的,中午在1907青岛老味道吃的午饭,非常不推荐的一家店。

      +

      吃完饭打车到奥帆中心,想着坐船玩一圈,到了发现因为风大停运了。

      +

      + +五四广场打卡 + + +

      +

      打卡完,直奔第三海水浴场玩水。

      +

      到了发现因为风大不让下水了,只能在沙滩上玩沙子了。

      +

      + +第三海水浴场 + + +

      +

      和娃一起抓了几只小螃蟹,虽然天气不好,娃玩的还是很开心。

      +

      + +赶海 + + +

      +

      实在不想吃海鲜了,晚饭在酒店附近吃了个米村拌饭,发现旁边有家类似北京的老年厨房,非常便宜,菜品也不错,第二天早晨在这里解决了。

      +

      天气不好,晚上在酒店看电视了,点了个麦当劳夜宵,结果娃没吃多少,全我吃了,给我撑的。

      +

      第三天天晴了,但是风还是大。

      +

      决定到第二海水浴场看看运气,到了之后还是不让下水,在沙滩上玩了会沙子。

      +

      时间差不多,回酒店办退房。

      +

      然后步行到火车站,上车回京。

      +

      青岛之行就此结束了,尽管行程很短,天气不太好,但一家人在一起就很开心,唯一的教训就是晚上夜宵不要吃的太多。 😂

      +]]>
      +
      + + 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ protoc-gen-go --version +</span></span><span style="display:flex;"><span>protoc-gen-go v1.34.2 +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ sh make.sh +</span></span><span style="display:flex;"><span>user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) +</span></span><span style="display:flex;"><span>$ +</span></span></code></pre></div><p>流水线编译报错,其中<code>make.sh</code>文件代码:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build +</span></span></code></pre></div><p>同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。</p> +
      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +$ 
      +$ protoc-gen-go --version
      +protoc-gen-go v1.34.2
      +$ 
      +$ sh make.sh
      +user.pb.go:123:45: undeclared name: any (requires version go1.18 or later)
      +$ 
      +

      流水线编译报错,其中make.sh文件代码:

      +
      ...
      +
      +protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto 
      +
      +...
      +
      +go build
      +

      同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。

      +

      登到流水线编译机器上,看了下go的版本已经是1.18.1了,按理不应该报这个错误的。

      +

      关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用master分支编译了一下,也还是报这个错误。

      +

      手动执行make.sh里的每条命令,发现是protoc编译pb文件时报的这个错误。

      +

      经过一番查找后,发现是protoc-gen-go在4月份更新了版本,引入了新特性。

      +

      protoc-gen-go’s versions

      +
      Versions in this module
      +v1
      +    v1.34.2 Jun 11, 2024
      +    v1.34.1 May 6, 2024
      +    v1.34.0 Apr 30, 2024
      +    v1.33.0 Mar 5, 2024
      +    v1.32.0 Dec 22, 2023
      +

      Protobuf Editions Overview

      +
      +

      Protobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = “proto2” or syntax = “proto3” at the top of proto definition files, you use an edition number, such as edition = “2024”, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.

      +

      Instead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.

      +
      +

      改用历史版本后解决。

      +
      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
      +
      ]]>
      +
      + + 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + Wed, 22 May 2024 21:20:20 +0800 + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + <h4 id="背景">背景</h4> +<p>4月底的时候,Livid大佬提醒,<code>Cloudflare</code>应该是调整了<code>IPFS Gateway</code>网关策略,我的<a href="https://liudon.xyz">IPFS镜像博客</a>无法访问了。</p> +<p>没查到<code>Cloudflare</code>的调整说明,不过还好<code>IPFS</code>官方也提供了公共网关<code>gateway.ipfs.io</code>,将域名解析改到官网网关。</p> + 背景 +

      4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。

      +

      没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。

      +

      但还是无法访问,被Cloudflare拦截了。

      +
      Error 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC
      +CNAME Cross-User Banned
      +What happened?
      +You've requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare's security policy.
      +
      +What can I do?
      +If this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2's documentation for details.
      +
      +
      +Visit our website to learn more about Cloudflare.
      +

      这周在Discord群里,看到有人发消息,说是Cloudflare将下线IPFS Gateway网关服务。

      +

      https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard

      +
      +

      All traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!

      +
      +

      方案调研

      +

      经过一番搜索,找到了一篇自建IPFS Gateway网关的资料,里面用到了bifrost-gateway组件。

      +
      To run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:
      +
      +$ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
      +

      看文档,可以通过这个命令搭建一个自己的网关服务,同时支持DNSLink方式访问。

      +

      太棒了,感觉可以自己搭一套网关,然后用Nginx反代对外提供服务。

      +

      在之前将博客部署到星际文件系统(IPFS)文章中,已经通过Kubo搭建了一套本地IPFS服务。

      +

      上机器验证一下可行性:

      +
        +
      1. +

        启动Bifrost Gateway,网关默认地址为https://127.0.0.1:8081

        +
        $ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
        +2024/05/22 20:54:00 Starting bifrost-gateway dev-build
        +2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080
        +2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024
        +2024/05/22 20:54:00 GRAPH_BACKEND: false
        +2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001
        +2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081
        +2024/05/22 20:54:00   Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
        +2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081
        +2024/05/22 20:54:00   Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/
        +2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus
        +
      2. +
      3. +

        在另外一个终端下,执行命令

        +
        $ curl 'http://127.0.0.1:8081/' -H"Host:liudon.xyz" -I
        +HTTP/1.1 200 OK
        +Accept-Ranges: bytes
        +Access-Control-Allow-Headers: Content-Type
        +Access-Control-Allow-Headers: Range
        +Access-Control-Allow-Headers: User-Agent
        +Access-Control-Allow-Headers: X-Requested-With
        +Access-Control-Allow-Methods: GET
        +Access-Control-Allow-Methods: HEAD
        +Access-Control-Allow-Methods: OPTIONS
        +Access-Control-Allow-Origin: *
        +Access-Control-Expose-Headers: Content-Length
        +Access-Control-Expose-Headers: Content-Range
        +Access-Control-Expose-Headers: X-Chunked-Output
        +Access-Control-Expose-Headers: X-Ipfs-Path
        +Access-Control-Expose-Headers: X-Ipfs-Roots
        +Access-Control-Expose-Headers: X-Stream-Output
        +Content-Length: 26283
        +Content-Type: text/html
        +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
        +Last-Modified: Wed, 22 May 2024 12:57:29 GMT
        +X-Ipfs-Path: /ipns/liudon.xyz/
        +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
        +Date: Wed, 22 May 2024 12:57:29 GMT
        +
      4. +
      +

      验证可行,不过我记得Kubo默认就有网关服务的,试一下直接通过Kubo默认网关的情况。

      +

      Kubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用

      +
      $ curl 'http://127.0.0.1:8080/' -H"Host:liudon.xyz" -I
      +HTTP/1.1 200 OK
      +Accept-Ranges: bytes
      +Access-Control-Allow-Headers: Content-Type
      +Access-Control-Allow-Headers: Range
      +Access-Control-Allow-Headers: User-Agent
      +Access-Control-Allow-Headers: X-Requested-With
      +Access-Control-Allow-Methods: GET
      +Access-Control-Allow-Origin: *
      +Access-Control-Expose-Headers: Content-Length
      +Access-Control-Expose-Headers: Content-Range
      +Access-Control-Expose-Headers: X-Chunked-Output
      +Access-Control-Expose-Headers: X-Ipfs-Path
      +Access-Control-Expose-Headers: X-Ipfs-Roots
      +Access-Control-Expose-Headers: X-Stream-Output
      +Content-Length: 26283
      +Content-Type: text/html
      +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
      +Last-Modified: Wed, 22 May 2024 12:59:25 GMT
      +X-Ipfs-Path: /ipns/liudon.xyz/
      +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
      +Date: Wed, 22 May 2024 12:59:25 GMT
      +

      也是可以的,那就没必要多搞一套bifrost网关了。

      +

      具体实现

      +

      通过Nginx反向代理转发到本地IPFS网关,只需要改一下解析就可以继续使用IPFS服务了。

      +

      + +方案 + + +

      +
        +
      1. Nginx反向代理
      2. +
      +
      server {
      +    listen 443 ssl http2;
      +    server_name liudon.xyz;
      +
      +    ssl_certificate /etc/nginx/ssl/liudon.xyz/fullchain.cer;
      +    ssl_certificate_key /etc/nginx/ssl/liudon.xyz/liudon.xyz.key;
      +
      +    ssl_protocols TLSv1.2 TLSv1.3;
      +    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
      +    ssl_prefer_server_ciphers on;
      +    ssl_session_cache shared:SSL:10m;
      +    ssl_session_timeout 10m;
      +
      +    location / {
      +            proxy_pass http://127.0.0.1:8080;
      +            proxy_set_header Host $host; // 注意这里要传递反代的域名信息,限制只能访问我们自己dnslink对应的资源
      +    }
      +
      +    access_log /var/log/nginx/liudon.xyz.access.log;
      +    error_log /var/log/nginx/liudon.xyz.error.log;
      +}
      +

      申请Let's Encrypt证书,证书相关的就不多做介绍了,网上资料很多。

      +
        +
      1. 更改DNS解析
      2. +
      +
      原有的解析
      +
      +类型:CNAME
      +名称:liudon.xyz
      +内容:cloudflare-ipfs.com
      +
      +新的解析
      +
      +类型:A
      +名称:liudon.xyz
      +内容:你的服务器公网IP
      +

      搞定,又可以继续白嫖IPFS服务了。

      +]]>
      +
      + + 302跳转的跨域问题(CORS) + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + Fri, 17 May 2024 20:13:57 +0800 + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + <p>302跳转的跨域问题</p> +<h4 id="场景一302不返回跨域头">场景一:302不返回跨域头</h4> +<p><strong>请求</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>GET /302 HTTP/1.1 +</span></span><span style="display:flex;"><span>Host: liudon.xyz +</span></span><span style="display:flex;"><span>Origin: https://www.baidu.com +</span></span><span style="display:flex;"><span>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 +</span></span></code></pre></div><p><strong>返回</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>HTTP/1.1 200 OK +</span></span><span style="display:flex;"><span>Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 +</span></span><span style="display:flex;"><span>Cf-Ray: 88535773eaf5107e-HKG +</span></span><span style="display:flex;"><span>Content-Length: 143 +</span></span><span style="display:flex;"><span>Content-Type: text/html +</span></span><span style="display:flex;"><span>Date: Fri, 17 May 2024 11:42:00 GMT +</span></span><span style="display:flex;"><span>Expires: Thu, 01 Jan 1970 00:00:01 GMT +</span></span><span style="display:flex;"><span>Location: https://liudon.org +</span></span><span style="display:flex;"><span>Server: cloudflare +</span></span><span style="display:flex;"><span>Vary: Accept-Encoding +</span></span></code></pre></div><p><strong>浏览器报错</strong></p> + 302跳转的跨域问题

      +

      场景一:302不返回跨域头

      +

      请求

      +
      GET /302 HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.xyz/302' from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 + + +

      +

      场景二:302跳转返回跨域头

      +

      请求

      +
      GET /302_return_origin_header HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Access-Control-Allow-Origin: https://www.baidu.com
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.org/' (redirected from 'https://liudon.xyz/302_return_origin_header') from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 return origin header + + +

      +

      注意,这里302跳转请求没有报错,是跳转后的连接报了跨域错误。

      +

      Location请求

      +
      GET / HTTP/1.1
      +Host: liudon.org
      +Origin: null
      +Referer: https://www.baidu.com/
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      + +location + + +

      +

      302返回了跨域头,所以浏览器请求了Location地址。

      +

      但为什么两次请求header头里的Origin字段值不一致呢?第二次Location请求为什么Origin字段值是null?

      +
      第一次:
      +Origin: https://www.baidu.com
      +
      +第二次
      +Origin: null
      +

      经过一番搜索,终于找到了一些资料。

      +
      +

      The Origin header value may be null in a number of cases, including (non-exhaustively):

      +

      Origins whose scheme is not one of http, https, ftp, ws, wss, or gopher (including blob, file and data). +Cross-origin images and media data, including that in , and elements. +Documents created programmatically using createDocument(), generated from a data: URL, or that do not have a creator browsing context. +Redirects across origins. +iframes with a sandbox attribute that doesn’t contain the value allow-same-origin. +Responses that are network errors. +Referrer-Policy set to no-referrer for non-cors request modes (e.g. simple form posts).

      +
      +

      出自 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description

      +
      +

      A request request has a redirect-tainted origin if these steps return true:

      +

      Let lastURL be null.

      +

      For each url of request’s URL list:

      +

      If lastURL is null, then set lastURL to url and continue.

      +

      If url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true.

      +

      Set lastURL to url. +Return false. +Serializing a request origin, given a request request, is to run these steps:

      +

      If request has a redirect-tainted origin, then return “null”.

      +

      Return request’s origin, serialized.

      +
      +

      出自 https://fetch.spec.whatwg.org/#concept-request-tainted-origin

      +

      简单说就是如果302跳转的域与上一次请求域不同的话,就会将Origin设置为null

      +]]>
      +
      + + GORM增加sqlcommenter特性 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + <p>什么是sqlcommenter?</p> +<blockquote> +<p>sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.</p> + 什么是sqlcommenter?

      +
      +

      sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

      +
      +

      GORM提供了hints组件,可以支持sqlcommenter

      +
      import "gorm.io/hints"
      +
      +DB.Clauses(hints.Comment("select", "master")).Find(&User{})
      +// SELECT /*master*/ * FROM `users`;
      +
      +DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
      +// SELECT * FROM `users` WHERE id = ? /* hint */
      +

      但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

      +

      我希望是全局增加sqlcommenter,业务侧不需要过多调整。

      +

      完整代码如下:

      +
      plugins/gorm.go
      +
      +package plugins
      +
      +import (
      +	"fmt"
      +
      +	gorm "gorm.io/gorm"
      +	gormclause "gorm.io/gorm/clause"
      +)
      +
      +type Comment struct {
      +	Content string
      +}
      +
      +func (c Comment) Name() string {
      +	return "COMMENT"
      +}
      +
      +func (c Comment) Build(builder gormclause.Builder) {
      +	builder.WriteString("/* ")
      +	builder.WriteString(c.Content)
      +	builder.WriteString(" */")
      +}
      +
      +func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
      +}
      +
      +func (c Comment) ModifyStatement(stmt *gorm.Statement) {
      +	clause := stmt.Clauses[c.Name()]
      +    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
      +    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
      +	clause.Expression = c
      +	stmt.Clauses[c.Name()] = clause
      +}
      +
      +var extraClause = []string{"COMMENT"}
      +
      +type CommentClausePlugin struct{}
      +
      +// NewCommentClausePlugin create a new ExtraPlugin
      +func NewCommentClausePlugin() *CommentClausePlugin {
      +	return &CommentClausePlugin{}
      +}
      +
      +// Name plugin name
      +func (ep *CommentClausePlugin) Name() string {
      +	return "CommentClausePlugin"
      +}
      +
      +// Initialize register BuildClauses
      +func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
      +	initClauses(db)
      +	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)
      +
      +	return
      +}
      +
      +func AddAnnotation(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +
      +	rid := "xx"
      +	// context上下文里取rid信息
      +	if v, ok := db.Statement.Context.Value("rid").(string); ok {
      +		rid = v
      +	}
      +
      +	content := fmt.Sprintf("rid=%s", rid)
      +
      +	if db.Statement.SQL.Len() > 0 {
      +		oldSQL := db.Statement.SQL.String()
      +		db.Statement.SQL.Reset()
      +		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
      +		return
      +	}
      +
      +	db.Statement.AddClause(Comment{Content: content})
      +}
      +
      +// initClauses init SQL clause
      +func initClauses(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +	createClause := append(extraClause, db.Callback().Create().Clauses...)
      +	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
      +	queryClause := append(extraClause, db.Callback().Query().Clauses...)
      +	updateClause := append(extraClause, db.Callback().Update().Clauses...)
      +	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
      +	rowClause := append(extraClause, db.Callback().Row().Clauses...)
      +	db.Callback().Create().Clauses = createClause
      +	db.Callback().Delete().Clauses = deleteClause
      +	db.Statement.Callback().Query().Clauses = queryClause
      +	db.Callback().Update().Clauses = updateClause
      +	db.Callback().Raw().Clauses = rawClause
      +	db.Callback().Row().Clauses = rowClause
      +}
      +
      +
      +main.go
      +package main
      +
      +import (
      +    "context"
      +    "plugins"
      +
      +    gorm "gorm.io/gorm"
      +    "github.com/google/uuid"
      +)
      +
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +
      +    db.Use(plugins.NewCommentClausePlugin())
      +
      +    db.Create(&Product{Code: "D42", Price: 100})
      +
      +    // 传入context,指定rid
      +    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
      +    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
      +}
      +

      阻塞了两天的问题,终于解决了!😁😁😁

      +

      how gorm generates sql

      +]]>
      +
      + + 源码分析:GORM是如何生成sql的 + https://liudon.com/posts/how-gorm-generates-sql/ + Thu, 18 Apr 2024 21:14:24 +0800 + https://liudon.com/posts/how-gorm-generates-sql/ + <p>在<code>gorm</code>下实现<a href="https://google.github.io/sqlcommenter/">sqlcommenter</a>过程中,遇到一些问题,顺便把<code>gorm</code>整个流程梳理了一遍,整理记录一下。</p> +<p>gorm使用示例</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>package main +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>import ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/driver/mysql&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/gorm&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>type Product struct { +</span></span><span style="display:flex;"><span> gorm<span style="color:#f92672">.</span>Model +</span></span><span style="display:flex;"><span> Code string +</span></span><span style="display:flex;"><span> Price uint +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> main() { +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">参考</span> https:<span style="color:#f92672">//</span>github<span style="color:#f92672">.</span>com<span style="color:#f92672">/</span>go<span style="color:#f92672">-</span>sql<span style="color:#f92672">-</span>driver<span style="color:#f92672">/</span>mysql<span style="color:#75715e">#dsn-data-source-name 获取详情</span> +</span></span><span style="display:flex;"><span> dsn :<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&amp;parseTime=True&amp;loc=Local&#34;</span> +</span></span><span style="display:flex;"><span> db, err :<span style="color:#f92672">=</span> gorm<span style="color:#f92672">.</span>Open(mysql<span style="color:#f92672">.</span>Open(dsn), <span style="color:#f92672">&amp;</span>gorm<span style="color:#f92672">.</span>Config{}) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> product Product +</span></span><span style="display:flex;"><span> db<span style="color:#f92672">.</span>First(<span style="color:#f92672">&amp;</span>product, <span style="color:#ae81ff">1</span>) <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">根据整型主键查找</span> +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>我们以<code>First</code>查询为例,看一下是怎么转成具体sql的。</p> + gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。

      +

      gorm使用示例

      +
      package main
      +
      +import (
      +  "gorm.io/driver/mysql"
      +  "gorm.io/gorm"
      +)
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
      +  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +  
      +  var product Product
      +  db.First(&product, 1) // 根据整型主键查找
      +}
      +

      我们以First查询为例,看一下是怎么转成具体sql的。

      +

      finisher_api.go文件,声明了First方法。

      +
      // First finds the first record ordered by primary key, matching given conditions conds
      +func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
      +	// 注册Order类型的Clause
      +	tx = db.Limit(1).Order(clause.OrderByColumn{
      +		Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
      +	})
      +	// 这里如果有指定条件的话,注册一个Where类型的Clause
      +	if len(conds) > 0 {
      +		if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
      +			tx.Statement.AddClause(clause.Where{Exprs: exprs})
      +		}
      +	}
      +	tx.Statement.RaiseErrorOnNotFound = true
      +	tx.Statement.Dest = dest
      +	return tx.callbacks.Query().Execute(tx)
      +}
      +

      gorm.go文件,可以找到tx.callbacks定义。

      +
      type Config struct {
      +	...
      +
      +	callbacks  *callbacks
      +	cacheStore *sync.Map
      +}
      +

      callbacks.go

      +
      // callbacks gorm callbacks manager
      +type callbacks struct {
      +	processors map[string]*processor
      +}
      +
      +type processor struct {
      +	db        *DB
      +	Clauses   []string
      +	fns       []func(*DB)
      +	callbacks []*callback
      +}
      +
      +type callback struct {
      +	name      string
      +	before    string
      +	after     string
      +	remove    bool
      +	replace   bool
      +	match     func(*DB) bool
      +	handler   func(*DB)
      +	processor *processor
      +}
      +
      +// 返回query类型的processor
      +func (cs *callbacks) Query() *processor {
      +	return cs.processors["query"]
      +}
      +
      +func (p *processor) Execute(db *DB) *DB {
      +	// call scopes
      +	for len(db.Statement.scopes) > 0 {
      +		db = db.executeScopes()
      +	}
      +
      +	var (
      +		curTime           = time.Now()
      +		stmt              = db.Statement
      +		resetBuildClauses bool
      +	)
      +
      +	// 注意这里的stmt.BuildClauses,后面会用到这个信息
      +	if len(stmt.BuildClauses) == 0 {
      +		stmt.BuildClauses = p.Clauses
      +		resetBuildClauses = true
      +	}
      +
      +	if optimizer, ok := db.Statement.Dest.(StatementModifier); ok {
      +		optimizer.ModifyStatement(stmt)
      +	}
      +
      +	// assign model values
      +	if stmt.Model == nil {
      +		stmt.Model = stmt.Dest
      +	} else if stmt.Dest == nil {
      +		stmt.Dest = stmt.Model
      +	}
      +
      +	// parse model values
      +	if stmt.Model != nil {
      +		if err := stmt.Parse(stmt.Model); err != nil && (!errors.Is(err, schema.ErrUnsupportedDataType) || (stmt.Table == "" && stmt.TableExpr == nil && stmt.SQL.Len() == 0)) {
      +			if errors.Is(err, schema.ErrUnsupportedDataType) && stmt.Table == "" && stmt.TableExpr == nil {
      +				db.AddError(fmt.Errorf("%w: Table not set, please set it like: db.Model(&user) or db.Table(\"users\")", err))
      +			} else {
      +				db.AddError(err)
      +			}
      +		}
      +	}
      +
      +	// assign stmt.ReflectValue
      +	if stmt.Dest != nil {
      +		stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
      +		for stmt.ReflectValue.Kind() == reflect.Ptr {
      +			if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
      +				stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
      +			}
      +
      +			stmt.ReflectValue = stmt.ReflectValue.Elem()
      +		}
      +		if !stmt.ReflectValue.IsValid() {
      +			db.AddError(ErrInvalidValue)
      +		}
      +	}
      +
      +	// 根据优先级执行不同callback的回调方法
      +	for _, f := range p.fns {
      +		f(db)
      +	}
      +
      +	if stmt.SQL.Len() > 0 {
      +		db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
      +			sql, vars := stmt.SQL.String(), stmt.Vars
      +			if filter, ok := db.Logger.(ParamsFilter); ok {
      +				sql, vars = filter.ParamsFilter(stmt.Context, stmt.SQL.String(), stmt.Vars...)
      +			}
      +			return db.Dialector.Explain(sql, vars...), db.RowsAffected
      +		}, db.Error)
      +	}
      +
      +	if !stmt.DB.DryRun {
      +		stmt.SQL.Reset()
      +		stmt.Vars = nil
      +	}
      +
      +	if resetBuildClauses {
      +		stmt.BuildClauses = nil
      +	}
      +
      +	return db
      +}
      +

      接下来,我们来看一下内置的callback是如何注册的。

      +

      mysql.go

      +
      var (
      +	// CreateClauses create clauses
      +	CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	// QueryClauses query clauses
      +	QueryClauses = []string{}
      +	// UpdateClauses update clauses
      +	UpdateClauses = []string{"UPDATE", "SET", "WHERE", "ORDER BY", "LIMIT"}
      +	// DeleteClauses delete clauses
      +	DeleteClauses = []string{"DELETE", "FROM", "WHERE", "ORDER BY", "LIMIT"}
      +
      +	defaultDatetimePrecision = 3
      +)
      +
      +...
      +
      +func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
      +	if dialector.DriverName == "" {
      +		dialector.DriverName = "mysql"
      +	}
      +
      +	if dialector.DefaultDatetimePrecision == nil {
      +		dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
      +	}
      +
      +	if dialector.Conn != nil {
      +		db.ConnPool = dialector.Conn
      +	} else {
      +		db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
      +		if err != nil {
      +			return err
      +		}
      +	}
      +
      +	withReturning := false
      +	if !dialector.Config.SkipInitializeWithVersion {
      +		err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
      +		if err != nil {
      +			return err
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "MariaDB") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportNullAsDefaultValue = true
      +			withReturning = checkVersion(dialector.ServerVersion, "10.5")
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.7.") {
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.") {
      +			dialector.Config.DisableDatetimePrecision = true
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "TiDB") {
      +			dialector.Config.DontSupportRenameColumnUnique = true
      +		}
      +	}
      +
      +	// register callbacks
      +	callbackConfig := &callbacks.Config{
      +		CreateClauses: CreateClauses,
      +		QueryClauses:  QueryClauses,
      +		UpdateClauses: UpdateClauses,
      +		DeleteClauses: DeleteClauses,
      +	}
      +
      +	if !dialector.Config.DisableWithReturning && withReturning {
      +		if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") {
      +			callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") {
      +			callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") {
      +			callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
      +		}
      +	}
      +
      +	// 注册默认callback
      +	callbacks.RegisterDefaultCallbacks(db, callbackConfig)
      +
      +	for k, v := range dialector.ClauseBuilders() {
      +		db.ClauseBuilders[k] = v
      +	}
      +	return
      +}
      +

      callbacks.go

      +
      var (
      +	createClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	queryClauses  = []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"}
      +	updateClauses = []string{"UPDATE", "SET", "WHERE"}
      +	deleteClauses = []string{"DELETE", "FROM", "WHERE"}
      +)
      +
      +type Config struct {
      +	LastInsertIDReversed bool
      +	CreateClauses        []string
      +	QueryClauses         []string
      +	UpdateClauses        []string
      +	DeleteClauses        []string
      +}
      +
      +func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
      +	enableTransaction := func(db *gorm.DB) bool {
      +		return !db.SkipDefaultTransaction
      +	}
      +
      +	if len(config.CreateClauses) == 0 {
      +		config.CreateClauses = createClauses
      +	}
      +	if len(config.QueryClauses) == 0 {
      +		config.QueryClauses = queryClauses
      +	}
      +	if len(config.DeleteClauses) == 0 {
      +		config.DeleteClauses = deleteClauses
      +	}
      +	if len(config.UpdateClauses) == 0 {
      +		config.UpdateClauses = updateClauses
      +	}
      +
      +    // 注册不同类型的callback
      +	createCallback := db.Callback().Create()
      +	createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	createCallback.Register("gorm:before_create", BeforeCreate)
      +	createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
      +	createCallback.Register("gorm:create", Create(config))
      +	createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
      +	createCallback.Register("gorm:after_create", AfterCreate)
      +	createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	createCallback.Clauses = config.CreateClauses
      +
      +	queryCallback := db.Callback().Query()
      +	queryCallback.Register("gorm:query", Query)
      +	queryCallback.Register("gorm:preload", Preload)
      +	queryCallback.Register("gorm:after_query", AfterQuery)
      +	queryCallback.Clauses = config.QueryClauses
      +
      +	deleteCallback := db.Callback().Delete()
      +	deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	deleteCallback.Register("gorm:before_delete", BeforeDelete)
      +	deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
      +	deleteCallback.Register("gorm:delete", Delete(config))
      +	deleteCallback.Register("gorm:after_delete", AfterDelete)
      +	deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	deleteCallback.Clauses = config.DeleteClauses
      +
      +	updateCallback := db.Callback().Update()
      +	updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
      +	updateCallback.Register("gorm:before_update", BeforeUpdate)
      +	updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
      +	updateCallback.Register("gorm:update", Update(config))
      +	updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
      +	updateCallback.Register("gorm:after_update", AfterUpdate)
      +	updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	updateCallback.Clauses = config.UpdateClauses
      +
      +	rowCallback := db.Callback().Row()
      +	rowCallback.Register("gorm:row", RowQuery)
      +	rowCallback.Clauses = config.QueryClauses
      +
      +	rawCallback := db.Callback().Raw()
      +	rawCallback.Register("gorm:raw", RawExec)
      +	rawCallback.Clauses = config.QueryClauses
      +}
      +

      到这里,默认callback就注册完成了,但是是如何转成对应sql的呢?

      +

      别急,我们继续往下看。

      +

      RegisterDefaultCallbacks方法里注册了一个gorm:query类型的callback,对应的回调方法为Query

      +

      query.go

      +
      func Query(db *gorm.DB) {
      +	if db.Error == nil {
      +		// 调用BuildQuerySQL方法
      +		BuildQuerySQL(db)
      +
      +		if !db.DryRun && db.Error == nil {
      +			rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
      +			if err != nil {
      +				db.AddError(err)
      +				return
      +			}
      +			defer func() {
      +				db.AddError(rows.Close())
      +			}()
      +			gorm.Scan(rows, db, 0)
      +		}
      +	}
      +}
      +
      +func BuildQuerySQL(db *gorm.DB) {
      +	if db.Statement.Schema != nil {
      +		for _, c := range db.Statement.Schema.QueryClauses {
      +			db.Statement.AddClause(c)
      +		}
      +	}
      +
      +	if db.Statement.SQL.Len() == 0 {
      +		db.Statement.SQL.Grow(100)
      +		clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
      +
      +		if db.Statement.ReflectValue.Kind() == reflect.Struct && db.Statement.ReflectValue.Type() == db.Statement.Schema.ModelType {
      +			var conds []clause.Expression
      +			for _, primaryField := range db.Statement.Schema.PrimaryFields {
      +				if v, isZero := primaryField.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
      +					conds = append(conds, clause.Eq{Column: clause.Column{Table: db.Statement.Table, Name: primaryField.DBName}, Value: v})
      +				}
      +			}
      +
      +			if len(conds) > 0 {
      +				db.Statement.AddClause(clause.Where{Exprs: conds})
      +			}
      +		}
      +
      +		if len(db.Statement.Selects) > 0 {
      +			clauseSelect.Columns = make([]clause.Column, len(db.Statement.Selects))
      +			for idx, name := range db.Statement.Selects {
      +				if db.Statement.Schema == nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				} else if f := db.Statement.Schema.LookUpField(name); f != nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: f.DBName}
      +				} else {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && len(db.Statement.Omits) > 0 {
      +			selectColumns, _ := db.Statement.SelectAndOmitColumns(false, false)
      +			clauseSelect.Columns = make([]clause.Column, 0, len(db.Statement.Schema.DBNames))
      +			for _, dbName := range db.Statement.Schema.DBNames {
      +				if v, ok := selectColumns[dbName]; (ok && v) || !ok {
      +					clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{Table: db.Statement.Table, Name: dbName})
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && db.Statement.ReflectValue.IsValid() {
      +			queryFields := db.QueryFields
      +			if !queryFields {
      +				switch db.Statement.ReflectValue.Kind() {
      +				case reflect.Struct:
      +					queryFields = db.Statement.ReflectValue.Type() != db.Statement.Schema.ModelType
      +				case reflect.Slice:
      +					queryFields = db.Statement.ReflectValue.Type().Elem() != db.Statement.Schema.ModelType
      +				}
      +			}
      +
      +			if queryFields {
      +				stmt := gorm.Statement{DB: db}
      +				// smaller struct
      +				if err := stmt.Parse(db.Statement.Dest); err == nil && (db.QueryFields || stmt.Schema.ModelType != db.Statement.Schema.ModelType) {
      +					clauseSelect.Columns = make([]clause.Column, len(stmt.Schema.DBNames))
      +
      +					for idx, dbName := range stmt.Schema.DBNames {
      +						clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +					}
      +				}
      +			}
      +		}
      +
      +		// inline joins
      +		fromClause := clause.From{}
      +		if v, ok := db.Statement.Clauses["FROM"].Expression.(clause.From); ok {
      +			fromClause = v
      +		}
      +
      +		if len(db.Statement.Joins) != 0 || len(fromClause.Joins) != 0 {
      +			if len(db.Statement.Selects) == 0 && len(db.Statement.Omits) == 0 && db.Statement.Schema != nil {
      +				clauseSelect.Columns = make([]clause.Column, len(db.Statement.Schema.DBNames))
      +				for idx, dbName := range db.Statement.Schema.DBNames {
      +					clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +				}
      +			}
      +
      +			specifiedRelationsName := make(map[string]interface{})
      +			for _, join := range db.Statement.Joins {
      +				if db.Statement.Schema != nil {
      +					var isRelations bool // is relations or raw sql
      +					var relations []*schema.Relationship
      +					relation, ok := db.Statement.Schema.Relationships.Relations[join.Name]
      +					if ok {
      +						isRelations = true
      +						relations = append(relations, relation)
      +					} else {
      +						// handle nested join like "Manager.Company"
      +						nestedJoinNames := strings.Split(join.Name, ".")
      +						if len(nestedJoinNames) > 1 {
      +							isNestedJoin := true
      +							gussNestedRelations := make([]*schema.Relationship, 0, len(nestedJoinNames))
      +							currentRelations := db.Statement.Schema.Relationships.Relations
      +							for _, relname := range nestedJoinNames {
      +								// incomplete match, only treated as raw sql
      +								if relation, ok = currentRelations[relname]; ok {
      +									gussNestedRelations = append(gussNestedRelations, relation)
      +									currentRelations = relation.FieldSchema.Relationships.Relations
      +								} else {
      +									isNestedJoin = false
      +									break
      +								}
      +							}
      +
      +							if isNestedJoin {
      +								isRelations = true
      +								relations = gussNestedRelations
      +							}
      +						}
      +					}
      +
      +					if isRelations {
      +						genJoinClause := func(joinType clause.JoinType, parentTableName string, relation *schema.Relationship) clause.Join {
      +							tableAliasName := relation.Name
      +							if parentTableName != clause.CurrentTable {
      +								tableAliasName = utils.NestedRelationName(parentTableName, tableAliasName)
      +							}
      +
      +							columnStmt := gorm.Statement{
      +								Table: tableAliasName, DB: db, Schema: relation.FieldSchema,
      +								Selects: join.Selects, Omits: join.Omits,
      +							}
      +
      +							selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
      +							for _, s := range relation.FieldSchema.DBNames {
      +								if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
      +									clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
      +										Table: tableAliasName,
      +										Name:  s,
      +										Alias: utils.NestedRelationName(tableAliasName, s),
      +									})
      +								}
      +							}
      +
      +							exprs := make([]clause.Expression, len(relation.References))
      +							for idx, ref := range relation.References {
      +								if ref.OwnPrimaryKey {
      +									exprs[idx] = clause.Eq{
      +										Column: clause.Column{Table: parentTableName, Name: ref.PrimaryKey.DBName},
      +										Value:  clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +									}
      +								} else {
      +									if ref.PrimaryValue == "" {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: parentTableName, Name: ref.ForeignKey.DBName},
      +											Value:  clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName},
      +										}
      +									} else {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +											Value:  ref.PrimaryValue,
      +										}
      +									}
      +								}
      +							}
      +
      +							{
      +								onStmt := gorm.Statement{Table: tableAliasName, DB: db, Clauses: map[string]clause.Clause{}}
      +								for _, c := range relation.FieldSchema.QueryClauses {
      +									onStmt.AddClause(c)
      +								}
      +
      +								if join.On != nil {
      +									onStmt.AddClause(join.On)
      +								}
      +
      +								if cs, ok := onStmt.Clauses["WHERE"]; ok {
      +									if where, ok := cs.Expression.(clause.Where); ok {
      +										where.Build(&onStmt)
      +
      +										if onSQL := onStmt.SQL.String(); onSQL != "" {
      +											vars := onStmt.Vars
      +											for idx, v := range vars {
      +												bindvar := strings.Builder{}
      +												onStmt.Vars = vars[0 : idx+1]
      +												db.Dialector.BindVarTo(&bindvar, &onStmt, v)
      +												onSQL = strings.Replace(onSQL, bindvar.String(), "?", 1)
      +											}
      +
      +											exprs = append(exprs, clause.Expr{SQL: onSQL, Vars: vars})
      +										}
      +									}
      +								}
      +							}
      +
      +							return clause.Join{
      +								Type:  joinType,
      +								Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName},
      +								ON:    clause.Where{Exprs: exprs},
      +							}
      +						}
      +
      +						parentTableName := clause.CurrentTable
      +						for _, rel := range relations {
      +							// joins table alias like "Manager, Company, Manager__Company"
      +							nestedAlias := utils.NestedRelationName(parentTableName, rel.Name)
      +							if _, ok := specifiedRelationsName[nestedAlias]; !ok {
      +								fromClause.Joins = append(fromClause.Joins, genJoinClause(join.JoinType, parentTableName, rel))
      +								specifiedRelationsName[nestedAlias] = nil
      +							}
      +
      +							if parentTableName != clause.CurrentTable {
      +								parentTableName = utils.NestedRelationName(parentTableName, rel.Name)
      +							} else {
      +								parentTableName = rel.Name
      +							}
      +						}
      +					} else {
      +						fromClause.Joins = append(fromClause.Joins, clause.Join{
      +							Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +						})
      +					}
      +				} else {
      +					fromClause.Joins = append(fromClause.Joins, clause.Join{
      +						Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +					})
      +				}
      +			}
      +
      +			db.Statement.AddClause(fromClause)
      +		} else {
      +			db.Statement.AddClauseIfNotExists(clause.From{})
      +		}
      +
      +		db.Statement.AddClauseIfNotExists(clauseSelect)
      +
      +		// db.Statement.BuildClauses眼熟吗?还记得前面的stmt.BuildClauses吗
      +		db.Statement.Build(db.Statement.BuildClauses...)
      +	}
      +}
      +

      重头戏终于来了,Query方法里调用了BuildQuerySQl,看名字也能猜到这里就是生成sql了,这里最终调用了db.Statement.Build方法。

      +

      statement.go

      +
      // Build build sql with clauses names
      +func (stmt *Statement) Build(clauses ...string) {
      +	var firstClauseWritten bool
      +
      +	for _, name := range clauses {
      +		if c, ok := stmt.Clauses[name]; ok {
      +			if firstClauseWritten {
      +				stmt.WriteByte(' ')
      +			}
      +
      +			firstClauseWritten = true
      +			if b, ok := stmt.DB.ClauseBuilders[name]; ok {
      +				b(c, stmt)
      +			} else {
      +				c.Build(stmt)
      +			}
      +		}
      +	}
      +}
      +

      这里会根据statementBuildCluauses属性,执行ClauseBuild方法。

      +

      clause.go

      +
      // ClauseBuilder clause builder, allows to customize how to build clause
      +type ClauseBuilder func(Clause, Builder)
      +
      +type Writer interface {
      +	WriteByte(byte) error
      +	WriteString(string) (int, error)
      +}
      +
      +// Builder builder interface
      +type Builder interface {
      +	Writer
      +	WriteQuoted(field interface{})
      +	AddVar(Writer, ...interface{})
      +	AddError(error) error
      +}
      +
      +// Clause
      +type Clause struct {
      +	Name                string // WHERE
      +	BeforeExpression    Expression
      +	AfterNameExpression Expression
      +	AfterExpression     Expression
      +	Expression          Expression
      +	Builder             ClauseBuilder
      +}
      +
      +// Build build clause
      +func (c Clause) Build(builder Builder) {
      +	if c.Builder != nil {
      +		c.Builder(c, builder)
      +	} else if c.Expression != nil {
      +		if c.BeforeExpression != nil {
      +			c.BeforeExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.Name != "" {
      +			builder.WriteString(c.Name)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.AfterNameExpression != nil {
      +			c.AfterNameExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		c.Expression.Build(builder)
      +
      +		if c.AfterExpression != nil {
      +			builder.WriteByte(' ')
      +			c.AfterExpression.Build(builder)
      +		}
      +	}
      +}
      +

      这里会执行对应Clause的Build方法。

      +
      // Select select attrs when querying, updating, creating
      +type Select struct {
      +	Distinct   bool
      +	Columns    []Column
      +	Expression Expression
      +}
      +
      +func (s Select) Name() string {
      +	return "SELECT"
      +}
      +
      +func (s Select) Build(builder Builder) {
      +	if len(s.Columns) > 0 {
      +		if s.Distinct {
      +			builder.WriteString("DISTINCT ")
      +		}
      +
      +		for idx, column := range s.Columns {
      +			if idx > 0 {
      +				builder.WriteByte(',')
      +			}
      +			builder.WriteQuoted(column)
      +		}
      +	} else {
      +		builder.WriteByte('*')
      +	}
      +}
      +
      +func (s Select) MergeClause(clause *Clause) {
      +	if s.Expression != nil {
      +		if s.Distinct {
      +			if expr, ok := s.Expression.(Expr); ok {
      +				expr.SQL = "DISTINCT " + expr.SQL
      +				clause.Expression = expr
      +				return
      +			}
      +		}
      +
      +		clause.Expression = s.Expression
      +	} else {
      +		clause.Expression = s
      +	}
      +}
      +

      这是Select类型的Clause定义,是不是一下就清楚了。

      +

      gorm通过callback里注册Clause,在Clause里实现了sql拼接操作。

      +

      看了几回源码,这次总算是搞清楚了。

      +]]>
      +
      + + 工银亚洲网银密码重置 + https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/ + Sat, 16 Mar 2024 10:09:59 +0800 + https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/ + <p>18年的时候办了张工银亚洲的银行卡,好几年没有用过了。</p> +<p>今年想起来了,发现网银登不上了,密码忘了。</p> +<p>最悲剧的是,试了超过10次,账户冻结了。</p> +<p>打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。</p> + 18年的时候办了张工银亚洲的银行卡,好几年没有用过了。

      +

      今年想起来了,发现网银登不上了,密码忘了。

      +

      最悲剧的是,试了超过10次,账户冻结了。

      +

      打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。

      +

      1月27日,东城出入境办理港澳通行证签注,排队半个小时左右,自助机操作。

      +

      顺路跑到附近可以见证开户的工行,说需要香港那边配合,人周末不上班,只能工作日。

      +

      电话西二旗支行,说是可以早上早点来办理,要不过年人多,没时间处理。

      +

      1月30日,早上直奔银行,赶在开门前到,第一个办理。

      +

      工作人员会指导你填写资料,填完后会给你两个信封,里面是重置后的网银密码。

      +

      1月31日下午,95588打电话,我以为是广告没接。

      +

      第二天早上反应过来,在路上的时候又打过来了。

      +

      香港那边直接电话,跟你确认身份信息后,下午就收到工行发来的已更新资料邮件。

      +

      其实这里密码就重置完了,因为我同时办理了更新通信地址,我以为是只更新了地址。

      +

      至此,整个流程就办完了,还是非常方便的。

      +

      另外,发现招银亚洲也没法登陆了,U盾过期了,又跑了一趟招行,重新办了个U盾。

      +

      好了,这下两个银行都可以正常使用了。

      +]]>
      +
      + + 加速Cloudflare访问 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + Wed, 21 Feb 2024 20:25:49 +0800 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + <h4 id="背景">背景</h4> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="blog.png" width="828" height="753" alt="博客架构" title="" loading="lazy" /> + </picture> + +</p> +<p>这是当前的博客架构,文件保存在<code>Github</code>仓库,通过<code>Cloudflare Page</code>提供访问。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="slow.png" width="1842" height="531" alt="国内访问情况" title="" loading="lazy" /> + </picture> + +</p> +<p>众所周知,在国内,<code>Cloudflare</code>的CDN属于反向加速,平均耗时在1.5s左右。</p> + 背景 +

      + +博客架构 + + +

      +

      这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。

      +

      + +国内访问情况 + + +

      +

      众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。

      +

      今天,我们就来讲一下,如何实现国内海外双线路博客访问。

      +

      大体思路

      +
      海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。
      +
      +Cloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。
      +
      +国内CDN添加域名后,也会提供一个cname域名B。
      +
      +使用国内dns解析服务,配置cname双线路解析。
      +

      具体操作

      +
        +
      1. +

        Cloudflare Page添加新域名解析

        +

        这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。

        +

        + +Cloudflare Page添加域名 + + +

        +
      2. +
      3. +

        配置国内CDN

        +

        我用的腾讯云,其他服务商也是可以的。

        +

        + +添加CDN域名 + + +

        +
        加速域名:填写博客对外访问的域名
        +回源地址和Host:填写第一步新加的域名
        +

        添加成功后,会有一个cname地址,这里是国内线路解析要用到的。

        +
      4. +
      5. +

        DNS解析调整

        +

        Cloudflare不支持双线路配置,国内服务商支持,我这里用的是腾讯云。

        +

        首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到Cloudflare的邮件,不需要理会。

        +
        liudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:
        +
        +sandals.dnspod.net
        +heron.dnspod.net
        +[not set]
        +[not set]
        +[not set]
        +
        +此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。
        +

        然后添加解析,默认走国内CDN,境外走Cloudflare Page

        +

        + +DNS双线路解析 + + +

        +
      6. +
      +

      额外的问题

      +

      为了加速Google Analytics,使用Cloudflare Worker进行了反代,具体见加速Google Analytics

      +

      更改NS后,导致海外访问无法触发Cloudflare Worker了,导致没有博客统计数据了。

      +

      经过一番搜索后,发现Cloudflare Page有类似的Function功能,只需要在网站根目录下新建functions目录,添加对应文件即可。

      +

      这里以Hugo静态博客举例说明:

      +

      在根目录的static目录下,新建functions目录,新建analytics目录,添加post.js文件。

      +

      这个analytics/post.js是为了对应原有Worker的访问地址analytics/post,可自行修改。

      +

      post.js文件代码如下:

      +
      export async function onRequest(context) {
      +    try {
      +        return await postHandler(context);
      +    } catch(e) {
      +        return new Response(`${e.message}\n${e.stack}`, { status: 500 }); 
      +    }
      +}
      +
      +async function postHandler(context) {
      +    const GA_DOMAIN = 'google-analytics.com';
      +    const GA_COLLECT_PATH = 'g\/collect';
      +    const COLLECT_PATH = 'analytics/post';
      +    const DOMAIN = '这里填你博客的域名';
      +
      +    const url = context.request.url;
      +    const cf_ip = context.request.headers.get('CF-Connecting-IP');
      +    const cf_country = context.request.cf.country;
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`)
      +    const newReq = await readRequest(context.request, ga_url);
      +    context.waitUntil(fetch(newReq));
      +
      +    return new Response(null, {
      +        status: 204,
      +        statusText: 'No Content',
      +      });
      +}
      +
      +async function readRequest(request, url) {
      +    const { _, headers } = request;
      +    const nq = {
      +      method: request.method,
      +      headers: {
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: request.body,
      +    };
      +    return new Request(url, nq);
      +}
      +

      优化效果

      +

      + +优化后的访问 + + +

      +

      有了国内CDN的加持,平均耗时优化到1s左右了。

      +]]>
      +
      + + 2023年终总结 + https://liudon.com/posts/review-2023/ + Thu, 04 Jan 2024 18:41:20 +0800 + https://liudon.com/posts/review-2023/ + <p>2023年过完了,是时候来个总结了。</p> +<h4 id="博客">博客</h4> +<p>2023年一共更新了15篇内容,共计12000字。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/review-2023/20240104184720_hu9917978577706645891.webp 733w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/review-2023/20240104184720_hu6551399417996237227.png 733w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20240104184720.png" width="733" height="436" alt="Google Analytics全年统计" title="" loading="lazy" /> + </picture> + +</p> +<p>访问Top3的文章:</p> + 2023年过完了,是时候来个总结了。

      +

      博客

      +

      2023年一共更新了15篇内容,共计12000字。

      +

      + +Google Analytics全年统计 + + +

      +

      访问Top3的文章:

      +

      将博客部署到星际文件系统(IPFS)

      +

      利用Github Actions定时抓取微博

      +

      优化博客的累计布局偏移(CLS)问题

      +

      主要是因为有在v2ex发帖导流,所以访问量高一些。

      +

      2023年12月北京暴雪记录

      +

      没想到的是一篇暴雪记录,收获了最多的评论,可能大家更容易共情。

      +

      不过从侧面也说明了技术的东西并没有太多人看,所以后来就不再分享导流了。

      +

      工作

      +

      今年搬到了后厂村,见识了互联网的人流。

      +

      在23年最后一个工作日,下班路上,算了一下,这一年晚上9点半之后打车59次。

      +

      而且年底这段时间,打车愈发困难,至少要排队1个小时。

      +

      相比之前一坐坐一天,每天中午会绕公司大楼转两圈。

      +

      + +23年步行统计 + + +

      +

      驾照

      +

      驾照终于到手了

      +

      趁着娃暑假、十一假期,开车上路了。

      +

      + +第一次开车上路 + + +

      +

      参与了第一次摇号。

      +

      + + + + +

      +

      换了新手机

      +

      用了5年的iPhone7 plus,过地铁NFC时不时刷不开了,感觉得换手机了。

      +

      十一假期回来在官网订了15pro,需要11月21日才能发货。

      +

      订货后,老手机的问题越来越多,换手机变的急迫起来。

      +

      于是,开始刷实体店取货。

      +

      用了探火app监控,10月11日中午抢到一台王府井取货。

      +

      10月12日晚上8点出发,上16号线地铁后,老手机开始持续发烫,过一会自动黑屏了。

      +

      按键有触感,但是屏幕不亮,怎么捣鼓也不行,手机越来越烫,都有点怕它炸了。

      +

      换乘8号线的路上,试了试按住所有按键,手机重启了,看见苹果logo那一刻真好。

      +

      重启完总算正常了,进店排队取货,提前了一个月拿到了新手机。

      +

      + +王府井Apple Store + + + + + +取完手机 + + +

      +

      升级体验非常好,使用丝滑,再也不用插着充电器玩手机了,感谢老婆。

      +

      + +大风 + + + + + +公司大楼 + + +

      +

      + +枫叶 + + + + + +秋 + + + + + +冬 + + + + + +冬 + + +

      +

      + +秋天的百望山 + + + + + +冬天的百望山 + + + + + +冬天的百望山 + + +

      +

      财务

      +

      提前还了两笔房贷,希望明年可以把商贷还完。

      +

      股市收益率-1.11%,港股套牢中,美股稍微回了点血。

      +

      + +股市收益 + + +

      +

      本命年了,希望一切顺利。

      +

      祝大家新年快乐!

      +]]>
      +
      + + 2023年12月北京暴雪记录 + https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/ + Sat, 16 Dec 2023 10:15:33 +0800 + https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/ + <blockquote> +<p>记录暴雪下普通打工人的生活。</p> +</blockquote> +<h4 id="12月14日-周四">12月14日 周四</h4> +<p>北京的雪已经连着下了两天了。</p> +<p>12月11日,也是因为下雪,晚上打车打到10点半才叫到车。</p> +<p>所以这次下雪后,晚上就早走了。</p> + +

      记录暴雪下普通打工人的生活。

      + +

      12月14日 周四

      +

      北京的雪已经连着下了两天了。

      +

      12月11日,也是因为下雪,晚上打车打到10点半才叫到车。

      +

      所以这次下雪后,晚上就早走了。

      +

      7:15坐上公司摆渡车,7:40左右到西二旗。

      +

      下车前刷微博看到有说昌平线故障,还没细看就到站下车了,还没想到事情的严重性。

      +

      路口已经有交警在指挥交通,看见两辆消防车在等红灯,事后想可能是去救援的。

      +

      走到进站口,就听到有人喊,“昌平线故障不停车,请更换其他交通工具”。

      +

      我坐13号线,继续往前走,就看到了下面的情景。

      +

      + +西二旗进站口 + + +

      +

      栏杆里全是人,后面的人还在不断的往里进。

      +

      看了下门口的人不动,人太多,不敢进去,先看看情况。

      +

      过了一会,有人喊13号线也停了,扭头赶紧走。

      +

      + +打车等候人数 + + +

      +

      第一反应打车,看了下,排队900人,直接放弃,换公交。

      +

      最近的公交站在西二旗大街,走吧。

      +

      到公交站一看,人更多,本来路就窄,全是人,走都没法走。

      +

      公交也没戏了,继续走吧,走过这段到前面看看再。

      +

      事后看说有的车被挤爆了,吓的别的车都不敢开门了。

      +

      走的有点冷,闻见一股香味,一看手机快8点半了,找个地吃点东西先。

      +

      + +吉祥馄饨 + + +

      +

      一碗馄饨下肚,暖和多了,继续上路。

      +

      + +路上 + + +

      +

      走的小腿有点酸了,终于到公交站了。

      +

      等了一小会,总算上车了。

      +

      车也不敢开的快,10迈左右。

      +

      晚上10点,终于到家了。

      +

      路上花费2小时45分钟

      +

      12月15日 周五

      +

      早上坐公交到地铁站。

      +
      + +
      +

      霍营地铁站盛况,人挤满了整个通道。

      +

      立马出站,换乘公交。

      +

      路上花费2小时

      +]]>
      +
      + + 使用Hugo实现响应式和优化的图片 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + Sun, 10 Dec 2023 08:29:05 +0800 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + <p>继续我们的<a href="../tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/">博客优化之旅</a>,本篇内容我们将介绍如何使用<code>Hugo</code>实现响应式和优化的图片。</p> +<h4 id="问题">问题</h4> +<p>在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>。</p> +<p>经过一段运行后,发现这里有一个弊端。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Run hugo --gc --minify --cleanDestinationDir +</span></span><span style="display:flex;"><span>Start building sites … +</span></span><span style="display:flex;"><span>hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer +</span></span></code></pre></div><p>随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。</p> + 继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

      +

      问题

      +

      在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

      +

      经过一段运行后,发现这里有一个弊端。

      +
      Run hugo --gc --minify --cleanDestinationDir
      +Start building sites … 
      +hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio
      +
      +ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
      +ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer
      +

      随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

      +

      失败的时候,需要手动重跑构建,自动化发布卡壳了。

      +

      优化

      +

      经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

      +
      +

      Image processing

      +

      Resize, crop, rotate, filter, and convert images.

      +

      https://gohugo.io/content-management/image-processing/

      +
      +

      下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

      +

      image processing需要用到Page bundles,所以文章目录结构需要调整, +将一篇文章的资源(md文件,图片等)放在一个目录下。

      +
      content/
      +├── posts
      +│   ├── my-post
      +│   │   ├── content1.md
      +│   │   ├── content2.md
      +│   │   ├── image1.jpg
      +│   │   ├── image2.png
      +│   │   └── index.md
      +│   └── my-other-post
      +│       └── index.md
      +

      目录结构调整完毕后,接下来修改图片显示文件代码。

      +

      这里需要生成webp格式图片,所以需要使用hugo的extended版本

      +

      PagerMod主题涉及到图片显示的一共三个文件:

      +
        +
      • +

        _default/_markup/render-image.html,对应markdown图片语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +{{ with $src := .Page.Resources.GetMatch .Destination }}
        +    {{- if $responsiveImages -}}
        +        <picture>
        +            /*只有使用了hugo扩展版本的,才生成webp格式图片*/
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +            <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +        </picture>
        +    {{- else }}
        +        <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +    {{- end }}
        +{{ end }}
        +
      • +
      • +

        partials/cover.html,对应文章封面解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    /*封面响应式图片配置开关,默认为true*/
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- if $responsiveImages -}}
        +        <picture>
        +        {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
        +        <source type="image/webp" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +            {{- end -}}
        +        {{- end -}}" sizes="{{ $dataSzes }}" />
        +        {{- end -}}
        +        <source type="{{ $cover.MediaType.Type }}" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if ge $cover.Width . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}
        +        {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
        +
        +        <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        </picture>
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      • +
      • +

        shortcodes/figure.html,对应文章内figure语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{ $src := .Get "src" }}
        +{{ $align := .Get "align" }}
        +{{ $alt := .Get "alt" }}
        +{{ $caption := .Get "caption" }}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
        +        {{- if eq (.Get "align") "center" }}align-center {{ end }}
        +        {{- with .Get "class" }}{{ . }}{{- end }}"
        +{{- end -}}>
        +    {{- if .Get "link" -}}
        +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        +    {{- end }}
        +    {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        +    <picture>
        +        {{- if $responsiveImages -}}
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +        {{- end }}
        +        <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
        +        {{- if or ($alt) ($caption) }}
        +        alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
        +        {{- end -}}
        +        {{- with $src.Width -}} width="{{ . }}"{{- end -}}
        +        {{- with $src.Height -}} height="{{ . }}"{{- end -}}
        +        /> <!-- Closing img tag -->
        +    </picture>
        +    {{ end }}
        +    {{- if .Get "link" }}</a>{{ end -}}
        +    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        +        <figcaption>
        +            {{ with (.Get "title") -}}
        +                {{ . }}
        +            {{- end -}}
        +            {{- if or (.Get "caption") (.Get "attr") -}}<p>
        +                {{- .Get "caption" | markdownify -}}
        +                {{- with .Get "attrlink" }}
        +                    <a href="{{ . }}">
        +                {{- end -}}
        +                {{- .Get "attr" | markdownify -}}
        +                {{- if .Get "attrlink" }}</a>{{ end }}</p>
        +            {{- end }}
        +        </figcaption>
        +    {{- end }}
        +</figure>
        +
      • +
      +

      使用效果

      +

      正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

      +
        +
      • markdown图片显示
      • +
      +

      + +render-image + + +

      +
        +
      • figure短代码显示
      • +
      +

      + +figure + + +

      +
        +
      • 封面显示
      • +
      +

      + +cover + + +

      +

      提示:

      +

      随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

      +
      Start building sites  
      +hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
      +Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
      +Built in 22356 ms
      +

      可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

      +
      buildDrafts: false
      +buildFuture: false
      +buildExpired: false
      +
      +timeout: 60s // 调大此处的时间即可
      +

      终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。

      +]]>
      +
      + + 加速Google Analytics + https://liudon.com/posts/optimize-google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/posts/optimize-google-analytics/ + <h3 id="起因">起因</h3> +<p><code>Google Analytics</code>是一款优秀的流量分析服务,集成方便,使用简单。</p> +<p>最近在优化页面访问速度,发现<code>Google Analytics</code>是一个优化点。</p> +<h3 id="优化">优化</h3> +<h4 id="1-访问加速">1. 访问加速</h4> +<p>国内访问<code>Google Analytics</code>很慢,同时还面临着各种广告屏蔽插件拦截。</p> + 起因 +

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      +

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      +

      优化

      +

      1. 访问加速

      +

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      +

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      +

      Cloudflare新建Worker,代码如下,保存后部署。

      +
      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      +
      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      +

      通过性能分析,发现js文件过大,影响页面加载速度。

      +

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      +

      搜索一番,找到一个瘦身版Google Analytics

      +
      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      +

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      +

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      +
      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      +

      + + + + +

      +]]>
      +
      + + 使用Google Indexing API加速博客收录 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + <p>对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。</p> +<p>今天,我们就来介绍一种利用<code>Google Indexing API</code>接口,通过<code>Github Actions</code>实现部署时通知<code>Google</code>抓取页面内容。</p> + 对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      +

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      +

      操作步骤:

      +
        +
      1. +

        申请Google API凭证

        +

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        +

        + + + + +

        +

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        +

        + + + + +

        +
        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        +

        + + + + +

        +

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        +

        点击后面的三个点按钮,选择管理密钥。

        +

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        +

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

        +
      2. +
      3. +

        将服务账号关联到Google Search Console

        +

        进入Google Search Console控制台,选择你的网站。

        +

        找到设置里的用户和权限,点击添加用户。

        +

        + + + + +

        +
        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      4. +
      5. +

        配置Github Action

        +
          +
        • +

          添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

          +
        • +
        • +

          编辑workflow编排任务,新增步骤

          +
        • +
        +
        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

        + + + + +

        +
      6. +
      +]]>
      +
      + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      + + 利用Github Actions定时抓取微博 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + Sat, 07 Oct 2023 13:17:57 +0800 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + <h4 id="背景">背景</h4> +<p>在微博上关注了一些用户,比如<a href="https://weibo.com/u/1401527553">tk教主</a>,<a href="https://weibo.com/u/1670659923">月风</a>。</p> +<p>但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。</p> +<h4 id="实现方案">实现方案</h4> +<p><strong>整体思路:利用<code>Github Actions</code>的<code>Scheduled</code>任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。</strong></p> + 背景 +

      在微博上关注了一些用户,比如tk教主月风

      +

      但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。

      +

      实现方案

      +

      整体思路:利用Github ActionsScheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。

      +
        +
      1. +

        新建仓库,比如weibo_archive

        +
      2. +
      3. +

        添加抓取脚本,完整代码

        +

        这里用到微博两个接口:

        +
        // 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断
        +https://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom&type=uid&value=$uid&containerid=107603$uid
        +
        +// 根据微博id,抓取微博完整的内容
        +https://m.weibo.cn/statuses/extend?id=$id
        +
      4. +
      5. +

        添加环境变量。

        +
          +
        • +

          进入个人设置->Developer Settings->Personal access tokens->Tokens (classic),创建新的Token,记下对应的值。

          +
        • +
        • +

          进入第一步创建仓库的配置页,点击Secrets and variables下的Actions

          +

          切到Secret目录,创建新的Secret变量,名称为TOKEN,值为前一步记录的值;切到Variables目录,创建新的Variables变量,名称为WEIBO_UIDS,值为你需要抓取的微博用户id,多个用户的话以|分割。

          +
        • +
        +
      6. +
      7. +

        添加定时任务,完整yaml文件如下。

        +
        # This is a basic workflow to help you get started with Actions
        +
        +name: CI
        +
        +# Controls when the workflow will run
        +on:
        +# Triggers the workflow on push or pull request events but only for the "main" branch
        +push:
        +    branches: [ "main" ]
        +pull_request:
        +    branches: [ "main" ]
        +
        +# Allows you to run this workflow manually from the Actions tab
        +workflow_dispatch:
        +schedule:
        +    - cron: '*/5 * * * *'
        +
        +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
        +jobs:
        +# This workflow contains a single job called "build"
        +build:
        +    # The type of runner that the job will run on
        +    runs-on: ubuntu-latest
        +
        +    permissions:      
        +    contents: write
        +
        +    # Steps represent a sequence of tasks that will be executed as part of the job
        +    steps:
        +    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
        +    - uses: actions/checkout@v3
        +
        +    # Runs a single command using the runners shell
        +    - name: archive weibo
        +        run: |
        +        chmod +x ./weibo_archive.sh
        +        ./weibo_archive.sh
        +        shell: bash
        +        env:
        +        weibo_uids: ${{ vars.weibo_uids }}
        +
        +    # Runs a set of commands using the runners shell
        +    - name: Commit changes
        +        uses: EndBug/add-and-commit@v9
        +        env:
        +        github_token: ${{ secrets.TOKEN }}
        +        add: .
        +
      8. +
      +

      效果

      +

      抓取后的内容,会按用户id分别保存到不同文件。

      +

      + +效果 + + +

      +

      不过这个方案有一个唯一的缺点,Github Actions定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。

      +
      +

      Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.

      +
      +]]>
      +
      + + 北大口腔牙周刮治记录 + https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/ + Sun, 17 Sep 2023 15:46:32 +0800 + https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/ + <h4 id="病情">病情</h4> +<p>上次洗完牙后,还是不时有出血的情况。</p> +<p>前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。</p> +<p>于是,又跑到医院来看牙了。</p> +<p>医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。</p> + 病情 +

      上次洗完牙后,还是不时有出血的情况。

      +

      前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。

      +

      于是,又跑到医院来看牙了。

      +

      医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。

      +

      回来查了下,牙周刮治还是挺痛苦的,想着找个专业医院看看再。

      +

      挂号

      +

      于是开始北大口腔挂号,中间挂了一次总院的普通号,但是网上好多说是规培生操作,又取消了。

      +

      找了农行的代挂号服务,北大口腔只能微信预约,他们也没渠道,只能自己挂了。

      +

      总院实在是挂不到,于是转战第一门诊部的号。

      +

      功夫不负有心人,7月5日总算挂上了牙周科李菲医生的号。

      +

      + + +

      +

      治疗

      +

      7月7日,第一次看医生,刮治是没的跑了。

      +

      + + +

      +

      做了牙齿探针,每个牙都要测一下,略疼。

      +

      开了治疗计划,刮治分两次,每次半口牙,总费用3000元左右。

      +

      第一次只洗牙,确实是专业,洗牙不到半个小时搞定。

      +

      最关键的,洗完后我刷牙确实很少再出血了。

      +

      8月3日,第一次刮治,只做了右半边。

      +

      医生说我这算是轻的,刮治比洗牙会疼一些,可以打麻药也可以不打。

      +

      行吧,那咱就不打麻药,来吧。

      +

      医生开始操作,感觉和洗牙是一样的,也是拿喷水的机器在牙上弄来弄去。

      +

      但是比洗牙更深一些,感觉是直接到牙龈下面了,确实要更疼一些。

      +

      洗完后,再拿一个铁丝一样的东西在牙上使劲刮来刮去。

      +

      自我感觉,还是前面洗牙更痛苦一些,后面刮来刮去没什么感觉。

      + + + + + +
      +
      + 铁丝一样的工具,第二次刮治拍的 +
      +
      + +

      半个小时搞定,本来我以为我这得一俩小时,医生说那得是非常严重的,你这属于轻的。

      +

      9月2日,第二次刮治,做了左半边。

      +

      左边牙龈问题更压重些,这次痛感更强烈一些。

      +

      同样也是半个小时搞定,1个月后再来复诊。

      +

      费用

      +
      洗牙:480元,单子找不到了,我记得是这个数。
      +
      +第一次刮治:1286元
      +
      +第二次刮治:1176元
      +

      总费用2942元,基本都走了医保。

      +]]>
      +
      + + 故乡回忆之旅 + https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/ + Sat, 09 Sep 2023 23:19:03 +0800 + https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/ + <p>赶在8月底,趁着娃暑假的尾声,回了趟老家。</p> +<p>老家有条俗语,“永福庄的街,三里长”。</p> +<p>这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。</p> +<p>小时候,整天在这条街上跑来跑去。</p> + 赶在8月底,趁着娃暑假的尾声,回了趟老家。

      +

      老家有条俗语,“永福庄的街,三里长”。

      +

      这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。

      +

      小时候,整天在这条街上跑来跑去。

      +

      我们那巷子基本很少是堵头的,你可以穿到任意一个巷子,最后还能回到大街。

      +

      小时候的大街,感觉很宽很长。

      +

      现在大街都垫高了,大街也变的窄了许多。

      +

      大街上的人很少,好多店铺都关门了。

      +

      走在大街上,小时候的一幕幕映在眼前。

      +

      有的地方,已经拆了重盖;

      +

      有的地方,已经人去楼空。

      +

      + + + + +

      + + + + +
      + 爷爷待过的大队部 +
      +
      + + + + + + +
      + + + + +
      + 以前的供销社 +
      +
      +

      +

      + + + + +

      + + + + +
      + 老爷爷家 +
      +
      + + + + + + +
      + + + + +
      + 姥爷以前的药铺 +
      +
      + + + + + + +
      + + + + +
      + 姥姥家 +
      +
      +

      +

      + + + + +

      + + + + +
      + 最早是肉食店,后来开过药铺 +
      +
      + + + + + + +
      + + + + +
      + 里面以前是个学校 +
      +
      +

      +

      + + + + +

      + + + + +
      + 巷口的大饭店 +
      +
      + + + + + + +
      + + + + +
      + 一栋风格怪异的楼 +
      +
      + + + + + + +
      + + + + +
      + 巷子 +
      +
      +

      +

      + + + + +

      + + + + +
      + 刘家祖庙 +
      +
      + + + + + + +
      + + + + +
      + 赵家祖庙 +
      +
      +

      +

      + + + + +

      + + + + +
      + 赵家祖庙 +
      +
      + + + + + + +
      + + + + +
      + 关帝庙 +
      +
      +

      +]]>
      +
      + + 解决Golang使用go get安装包后找不到可执行文件的问题 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + Thu, 17 Aug 2023 09:20:50 +0800 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + <h4 id="背景">背景</h4> +<p>编译流水线代码</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go get google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build -o xxx +</span></span></code></pre></div><p>在go升级到1.20.1版本后,执行报错。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protoc-gen-go: program not found or is not executable +</span></span></code></pre></div><h4 id="解决">解决</h4> +<blockquote> +<p>Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.</p> +<p>In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.</p> + 背景 +

      编译流水线代码

      +
      go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +
      +protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto
      +
      +...
      +
      +go build -o xxx
      +

      在go升级到1.20.1版本后,执行报错。

      +
      protoc-gen-go: program not found or is not executable
      +

      解决

      +
      +

      Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.

      +

      In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.

      +

      https://docs.studygolang.com/doc/go-get-install-deprecation

      +
      +

      从 Go 1.7 版本开始,go get 命令默认只会下载包,不会自动编译和安装可执行文件。

      +

      因此,如果你想要使用 go get 命令安装包并编译可执行文件,你需要使用 go install 命令。

      +

      替换为go install解决。

      +]]>
      +
      + + 修正Hugo的JSON Feed格式 + https://liudon.com/posts/fix-hugo-json-feed/ + Sat, 25 Mar 2023 14:11:18 +0800 + https://liudon.com/posts/fix-hugo-json-feed/ + <h4 id="问题背景">问题背景</h4> +<p>前几天在<a href="https://planetics.xyz/">Planet</a>里follow自己的<a href="https://liudon.eth">web3博客</a>,遇到下面的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp 716w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png 716w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202303251415675.png" width="716" height="544" alt="PlanetFeedError" title="" loading="lazy" /> + </picture> + +</p> +<p>经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。</p> +<p>因为我的已经修正没法截图,这里以<a href="https://dvel.me/index.json">dvel的博客</a>举例,格式类似如下。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[ +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> &#34;content&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&amp;#34;}], &amp;#34;temperature&amp;#34;: 0.7 }&amp;#39; ``` 响应格式为: ``` { &amp;#34;id&amp;#34;:&amp;#34;chatcmpl-abc123&amp;#34;, &amp;#34;object&amp;#34;:&amp;#34;chat.completion&amp;#34;, &amp;#34;created&amp;#34;:1677858242, &amp;#34;model&amp;#34;:&amp;#34;gpt-3.5-turbo-0301&amp;#34;, &amp;#34;usage&amp;#34;:{ &amp;#34;prompt_tokens&amp;#34;:13, &amp;#34;completion_tokens&amp;#34;:7, &amp;#34;total_tokens&amp;#34;:20 }, &amp;#34;choices&amp;#34;:[ { &amp;#34;message&amp;#34;:{ &amp;#34;role&amp;#34;:&amp;#34;assistant&amp;#34;, &amp;#34;content&amp;#34;:&amp;#34;\\n\\nThis is a test!&amp;#34; }, &amp;#34;finish_reason&amp;#34;:&amp;#34;stop&amp;#34;, &amp;#34;index&amp;#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n&#34;, +</span></span><span style="display:flex;"><span> &#34;permalink&#34;: &#34;https://dvel.me/posts/chd-quiz-answer/&#34;, +</span></span><span style="display:flex;"><span> &#34;summary&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&#34;, +</span></span><span style="display:flex;"><span> &#34;title&#34;: &#34;CHD 油猴脚本:每日签到自动答题&#34; +</span></span><span style="display:flex;"><span> }, +</span></span><span style="display:flex;"><span> ... +</span></span><span style="display:flex;"><span>] +</span></span></code></pre></div><p>下面是一个<code>JSON Feed</code>的示例,详细规范见<a href="https://www.jsonfeed.org/">jsonfeed.org</a>。</p> + 问题背景 +

      前几天在Planet里follow自己的web3博客,遇到下面的错误。

      +

      + +PlanetFeedError + + +

      +

      经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。

      +

      因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。

      +
      [
      +  {
      +    "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n",
      +    "permalink": "https://dvel.me/posts/chd-quiz-answer/",
      +    "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!",
      +    "title": "CHD 油猴脚本:每日签到自动答题"
      +  },
      +  ...
      +]
      +

      下面是一个JSON Feed的示例,详细规范见jsonfeed.org

      +
      {
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": "My Example Feed",
      +    "home_page_url": "https://example.org/",
      +    "feed_url": "https://example.org/feed.json",
      +    "items": [
      +        {
      +            "id": "2",
      +            "content_text": "This is a second item.",
      +            "url": "https://example.org/second-item"
      +        },
      +        {
      +            "id": "1",
      +            "content_html": "<p>Hello, world!</p>",
      +            "url": "https://example.org/initial-post"
      +        }
      +    ]
      +}
      +

      修复方案

      +
        +
      1. 添加自定义jsonfeed模版文件,路径为layouts/_default/index.jsonfeed.json
      2. +
      +

      文件内容如下:

      +
      {{- $pctx := . -}}
      +{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}
      +{{- $pages := slice -}}
      +{{- if or $.IsHome $.IsSection -}}
      +{{- $pages = $pctx.RegularPages -}}
      +{{- else -}}
      +{{- $pages = $pctx.Pages -}}
      +{{- end -}}
      +{{- $limit := site.Config.Services.RSS.Limit -}}
      +{{- if ge $limit 1 -}}
      +{{- $pages = $pages | first $limit -}}
      +{{- end -}}
      +{{- $title := "" }}
      +{{- if eq .Title .Site.Title }}
      +{{- $title = .Site.Title }}
      +{{- else }}
      +{{- with .Title }}
      +{{- $title = print . " on "}}
      +{{- end }}
      +{{- $title = print $title .Site.Title }}
      +{{- end }}
      +{
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": {{ $title | jsonify }},
      +    "home_page_url": {{ .Permalink | jsonify }},
      +    {{- with  .OutputFormats.Get "jsonfeed" }}
      +    "feed_url": {{ .Permalink | jsonify  }},
      +    {{- end }}
      +    {{- if (or .Site.Params.author .Site.Params.author_url) }}
      +    "authors": [{
      +      {{- if .Site.Params.author }}
      +        "name": {{ .Site.Params.author | jsonify }},
      +      {{- end }}
      +      {{- if .Site.Params.author_url }}
      +        "url": {{ .Site.Params.author_url | jsonify }}
      +      {{- end }}
      +    }],
      +    {{- end }}
      +    {{- if $pages }}
      +    "items": [
      +        {{- range $index, $element := $pages }}
      +        {{- with $element }}
      +        {{- if $index }},{{end}} {
      +            "title": {{ .Title | jsonify }},
      +            "id": {{ .Permalink | jsonify }},
      +            "url": {{ .Permalink | jsonify }},
      +            {{- if .Site.Params.showFullTextinJSONFeed }}
      +            "summary": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            "content_html": {{ .Content | jsonify }},
      +            {{- else }}
      +            "content_text": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            {{- end }}
      +            {{- if .Params.cover.image }}
      +            {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
      +            {{- if $cover }}
      +            "image": {{ (path.Join .RelPermalink $cover) | absURL | jsonify }},
      +            {{- end }}
      +            {{- end }}
      +            "date_published": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
      +            {{- $tags := slice -}}
      +            {{ with .Params.tags }}
      +            {{ range . }}
      +            {{ $tags = $tags | append (. | jsonify) }}
      +            {{end}}
      +            {{end}}
      +            "tags": [{{ delimit $tags ", " }}]
      +        }
      +        {{- end }}
      +        {{- end }}
      +    ]
      +    {{ end }}
      +}
      +
        +
      1. 开启JSON Feed
      2. +
      +

      配置文件调整如下:

      +
      outputFormats:
      +  jsonfeed: # 添加jsonfeed输出格式
      +    mediaType: application/feed+json
      +    baseName: feed
      +    rel: alternate
      +    isPlainText: true
      +
      +outputs:
      +    home:
      +        - HTML
      +        - RSS
      +        - json # fusejs搜索依赖index.json,不要漏掉这个配置
      +        - jsonfeed # 开启jsonfeed
      +
      +params:
      +    ...
      +
      +    showFullTextinJSONFeed: true # jsonfeed开启全文输出
      +

      参考资料:How to add JSON Feed support to Hugo

      +]]>
      +
      + + 我的学车之路 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + Fri, 24 Mar 2023 20:48:24 +0800 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + <p>之前在<a href="https://liudon.com/posts/review-2022/">2022年终总结</a>提到过,我在练车考驾照。</p> +<p>就在昨天,终于拿证了。👏👏👏</p> +<p>咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月12日,科目一考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月22日,科目二模拟驾驶。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月13日,科目二第一次上车练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月4日,年后驾校恢复培训,继续科目二练车。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月13日,科目二考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月11日,科目三上路练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月23日,上午科目三考试通过,下午科目四考试通过。 +</span></span><span style="display:flex;"><span>考试的时候,早上遇到临时交通管制,一直到9点40才开考。 +</span></span><span style="display:flex;"><span>考完回来,班车上的人说又管制不能考了。 +</span></span><span style="display:flex;"><span>班车拉回驾校,剩下的人中午加班考试。 +</span></span></code></pre></div><p>终于不用再5点半起床赶班车了。🥱</p> + 之前在2022年终总结提到过,我在练车考驾照。

      +

      就在昨天,终于拿证了。👏👏👏

      +

      咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶

      +
      2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。
      +
      +2022年10月12日,科目一考试通过。
      +
      +2022年10月22日,科目二模拟驾驶。
      +
      +2022年11月13日,科目二第一次上车练习。
      +
      +2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。
      +
      +2023年2月4日,年后驾校恢复培训,继续科目二练车。
      +
      +2023年2月13日,科目二考试通过。
      +
      +2023年3月11日,科目三上路练习。
      +
      +2023年3月23日,上午科目三考试通过,下午科目四考试通过。
      +考试的时候,早上遇到临时交通管制,一直到9点40才开考。
      +考完回来,班车上的人说又管制不能考了。
      +班车拉回驾校,剩下的人中午加班考试。
      +

      终于不用再5点半起床赶班车了。🥱

      +

      + +本本到手啦 + + +

      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      + + 新冠疫情后的第一个春节 + https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/ + Thu, 16 Feb 2023 20:56:38 +0800 + https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/ + <blockquote> +<p>下面的内容是由chatGPT润色生成的。</p> +<p>AI太强大了 😂</p> +</blockquote> +<p>当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。</p> +<p>但我从未想过,等我长大后,我也会成为其中的一员。</p> + +

      下面的内容是由chatGPT润色生成的。

      +

      AI太强大了 😂

      + +

      当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。

      +

      但我从未想过,等我长大后,我也会成为其中的一员。

      +

      由于疫情的影响,我已经连续三年不能回家过年了。

      +

      每次我告诉父母我无法回家,他们总是表现得非常平静,但我不知道他们挂了电话后的心情会如何。

      +

      直到今年,我们全家都经历了一次感染,但这也使我们有了机会在这个特殊的春节回家过年。

      +

      提前请了假,带娃体验下老家的生活。

      +

      + +回家 + + +

      +

      回家啦。

      +

      + +赶集 + + +

      +

      赶大集。

      +

      + + +

      +

      烧火。

      +

      + +放烟花 + + +

      +

      放烟花。

      +

      + +抓鸟 + + +

      +

      抓鸟。

      +

      + +放孔明灯 + + +

      +

      放孔明灯。

      +

      + +蹭饭 + + +

      +

      邻居家蹭饭。

      +]]>
      +
      + + 第一次清理键盘 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + Mon, 16 Jan 2023 14:43:38 +0800 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + <p>19年生日的时候,媳妇送了一款flico的机械键盘。</p> +<p>这次搬家后,想着年前清理下键盘,实在是太脏了。</p> +<p>周五下班,带上键盘回家。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202301161450523.jpeg" width="1706" height="1279" alt="" title="" loading="lazy" /> + </picture> + +</p> + 19年生日的时候,媳妇送了一款flico的机械键盘。

      +

      这次搬家后,想着年前清理下键盘,实在是太脏了。

      +

      周五下班,带上键盘回家。

      +

      + + + + +

      +

      键盘全貌,上面好多油。

      +

      + + + + +

      +

      开拆。

      +

      + + + + +

      +

      手还是太慢,上工具吧。

      +

      + + + + +

      +

      全部拆完。

      +

      + + + + +

      +

      内部特写。

      +

      + + + + +

      +

      清理出来的灰屑、头发,这键盘见证了我的发迹线变化

      +

      + + + + +

      +

      终于清理干净了。

      +

      + + + + +

      +

      复原,又可以咔咔写代码了。

      +]]>
      +
      + + 2022年终总结 + https://liudon.com/posts/review-2022/ + Thu, 12 Jan 2023 07:21:20 +0800 + https://liudon.com/posts/review-2022/ + <p>2022年已经过去1周多了,记录一下我的2022年。</p> +<h4 id="疫情">疫情</h4> +<p>2022年,是新冠疫情的第三年,也是切身感受到的一年。</p> +<p>3月22日晚,8点半和同事刚上13号线地铁。</p> + 2022年已经过去1周多了,记录一下我的2022年。

      +

      疫情

      +

      2022年,是新冠疫情的第三年,也是切身感受到的一年。

      +

      3月22日晚,8点半和同事刚上13号线地铁。

      +

      紧接着看到群里有人说,公司大厦因为疫情封控了,只进不出。

      +

      + +大厦封控 + + +

      +

      第一次感受弹窗3,居家隔离。

      +

      + +弹窗3 + + +

      +

      + +居家隔离 + + +

      +

      + +居家观察 + + +

      +

      5月21日,开启居家办公。

      +

      6月5日,开始到岗工作。

      +

      11月17日,公司通知第二天居家办公。

      +

      11月21日,小区通知临时管控。

      +

      + +小区封控 + + +

      +

      12月10日,媳妇中招。

      +

      12月12日,自己中招。

      +

      12月18日,娃中招。

      +

      + + +

      +

      12月20日,开始到岗工作,持续了近一个月的居家隔离生活终于结束。

      +

      老妈冒着北京疫情高峰感染的风险,过来帮我们带娃。

      +

      从1周一检,到3天一检,不知道健康宝有没有年终报告,告诉你今年做了多少次核酸,相信会是很棒的一个数字。

      +

      入学

      +

      上半年赶着疫情的间隙,整理好了娃的入学资料。

      +

      经过一个月的焦虑等待后,最终被附近的学校录取,也确实感受到了离家近的好处。

      +

      详细经过见记录2022年海淀幼升小

      +

      休假

      +

      春节没有回家过年,上半年北京和老家交替出现疫情,最终在8月份休假回了趟家。

      +

      驾照

      +

      因为疫情,感觉还是得考个驾照,拖延了N年的事项提上了日程。

      +

      6月份报名,一直拖到10月份才过的科目一。

      +

      11月底开始摸车了,刚上2节课因为疫情封校了,现在学的都快忘光了。

      +

      搬家

      +

      年底公司通知搬家,又搬回了银科,兜兜转转,又回到了起点。

      +

      + +又回银科 + + +

      +

      博客

      +

      2022年,把博客又捡了回来,今年多更新吧。

      +

      + + + + +

      +

      或许是上了年纪,2022年发现泪点变得很低,健康、家人才是最重要的。

      +

      新的一年,继续开源节流,做好防护,保护好家人。

      +

      最后,祝大家新年快乐!

      +]]>
      +
      + + 去掉Cloudflare烦人的email-decode.min.js请求 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + Fri, 26 Aug 2022 23:25:57 +0800 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + <p>通过WebPageTest页面测试,发现一个<code>/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js</code>的文件请求,影响到了页面渲染。</p> + 通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。

      +

      + +webpagetest测试结果 + + +

      +

      看路径像是Cloudflare的文件,搜了下主题代码,没找到相关文件。

      +

      经过一番搜索,原来这个是Cloudflare的电子邮件地址混淆技术功能。

      +

      + +Scrape Shield + + +

      +

      关闭这个功能即可。

      +]]>
      +
      + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="../posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 优化博客的累计布局偏移(CLS)问题 + https://liudon.com/posts/fix-blog-cls/ + Sat, 20 Aug 2022 07:27:22 +0800 + https://liudon.com/posts/fix-blog-cls/ + <p>此文已过期,优化方案参考<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>.</p> +<h4 id="问题表现">问题表现</h4> +<p>7月份将博客部署由<code>Github</code>迁移到<code>Cloudflare</code>后,开始关注博客的性能问题。</p> +<p>偶然看到苏卡卡大佬的<a href="https://blog.skk.moe/post/fix-blog-cls/">CLS优化文章</a>,拿自己博客也测试了下,发现也存在同样的问题。</p> + 此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      +

      问题表现

      +

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      +

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      +

      + +Lighthouse测试报告 + + +

      +

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      +

      为了解决这个问题,需要指定封面的宽高参数。

      +

      + +cover.html code + + +

      +

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      +

      解决方案

      +
        +
      1. +

        新增封面配置

        +

        文章封面配置新增widthheight属性。

        +
        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. +
      3. +

        自定义封面文件

        +

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

        +
            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        +

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        +

        + +cos-img-process + + +

        +
      4. +
      5. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      6. +
      +

      再进一步

      +

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      +

      基于markdown语法的图片代码,是不支持宽高参数的。

      +

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      +

      + +figure.html code + + +

      +

      我们使用figure语法插入图片,指定图片宽高。

      +

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

      +
          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      +
      +

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      +
      +

      + +gtmetrix-result + + +

      +

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      +]]>
      +
      + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      + + 奥林匹克公园向日葵之旅 + https://liudon.com/posts/olympic-park-sunflower-tour/ + Thu, 21 Jul 2022 20:40:20 +0800 + https://liudon.com/posts/olympic-park-sunflower-tour/ + <p>媳妇有事回老家了,这两天自己带娃。</p> +<p>小区群里有人说奥林匹克公园的向日葵开了,适合拍照。</p> +<p>正好周六多云,没有太阳,出门遛娃。</p> +<p>带上我好久不用的相机,省得发霉了。</p> + 媳妇有事回老家了,这两天自己带娃。

      +

      小区群里有人说奥林匹克公园的向日葵开了,适合拍照。

      +

      正好周六多云,没有太阳,出门遛娃。

      +

      带上我好久不用的相机,省得发霉了。

      +

      以往都是去的南园,第一次来北园。

      +

      西门进入,沿着路往右走,一会就到。

      +

      人很多,估计大家都因为之前疫情在家憋疯了。

      +

      到了没多久,太阳又出来了,超级晒。

      +

      提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。

      +

      + +向日葵 + + + + + +向日葵 + + + + + +荷花 + + + + + +荷叶上的蜻蜓 + + +

      +]]>
      +
      + + 记第二次洗牙 + https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/ + Tue, 21 Jun 2022 21:58:49 +0800 + https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/ + <p>最近刷牙的时候,牙龈总是出血。</p> +<p>距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。</p> +<p>上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。</p> + 最近刷牙的时候,牙龈总是出血。

      +

      距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。

      +

      上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。

      +

      这次决定去医院看看,提前挂了号。

      +

      医生看过后,说得洗牙,不过当天只能先做洗牙前的检查,洗牙得预约。

      +

      开单子抽血检查,完事回家。

      +

      到了预约的日子,约的是4点,3点左右就到医院了。

      +

      15:57轮到我,进去一共20分钟完事。

      +

      我以为还有别的事项,结果医生说好了。

      +

      我说我感觉牙里卡着个东西,医生又拿起装备给我洗了一遍。

      +

      上次洗牙花了快1个小时,这也太快了。

      +

      洗牙费用380元,可以走医保报销。

      +

      以往都说医院洗牙贵,没成想外面私人的要更贵。

      +]]>
      +
      + + 记录2022年海淀幼升小 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220525202612.png" width="1920" height="2243" alt="20220525202612" title="" loading="lazy" /> + </picture> + +</p> +<blockquote> +<p>18年的热点新闻,纳税千万孩子无法在北京上学。</p> +</blockquote> +<p>一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。</p> + + +20220525202612 + + +

      +
      +

      18年的热点新闻,纳税千万孩子无法在北京上学。

      +
      +

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      +

      提前在网上搜了一番资料,都是一些机构整理的信息。

      +

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      +

      1. 信息采集

      +

      5月5日,采集系统开放。

      +

      当天下午录入相关信息,提交网上审核。

      +

      2. 网上审核

      +

      信息提交后,就开始了漫长的审核时间。

      +
      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      + +20220527202411 + + +

      +

      3. 线下审核

      +

      网上审核通过后,打印入学申请表,预约线下审核时间。

      +

      + +20220527202458 + + +

      +

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      +

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      +

      后来交流一番后,发现是自己理解错了。

      +

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      +
      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      + +街道审核 + + +

      +
        +
      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • +
      • 工作证明还需要提供满足时间要求的社保缴费记录。
      • +
      +

      4. 审核通过

      +

      5月27日,审核通过后,打印信息采集表。

      +

      + +20220527202545 + + +

      +

      5. 学校登记

      +

      6月1日对口学校发布入学登记通知书。

      +

      按通知书准备资料,到登记时间去学校交资料。

      +

      今年遇到疫情,改为线上邮件发送资料登记了。

      +

      6. 填报志愿

      +

      6月23日,海淀教育发文1911后填报志愿通知

      +

      + +20220629215840 + + +

      +

      第一志愿锁定,其他志愿自己选择填报。

      +

      6月25日锁定,不允许再修改。

      +
      +

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      +

      租房的不需要填报志愿,等待派位。

      +
      +

      7. 查看结果

      +

      + +20220629220638 + + +

      +

      6月29日15点,系统开放结果查询。

      +

      第一志愿录取,一直担心的调剂没有发生。

      +

      7月10日,收到教委短信,系统查询录取通知书。

      +

      + +20220710085749 + + +

      +

      + +20220710084342 + + +

      +

      历时1个多月的幼升小总算落地了。

      +]]>
      +
      + + Golang解析json的一个问题 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + Fri, 20 May 2022 21:18:23 +0800 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + <p>业务模块从<code>php</code>迁移到<code>golang</code>下了,最近遇到一个golang下json解析的问题:</p> +<pre><code>请求接口,按返回包字段判断请求成功与否。 +</code></pre> +<p>伪代码如下:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;encoding/json&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;fmt&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Response</span> <span style="color:#66d9ef">struct</span> { +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Code</span> <span style="color:#66d9ef">int</span> <span style="color:#e6db74">`json:&#34;code&#34;`</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Msg</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;msg&#34;`</span> +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() { +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景1,返回包符合接口要求 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;code&#34;:100,&#34;msg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析正确,符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:100 Msg:failed} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景2,返回包不符合接口要求,缺少相关字段 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> = <span style="color:#e6db74">`{&#34;retCode&#34;:100,&#34;retMsg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res1</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析错误,不符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:0 Msg:} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>} +</span></span></code></pre></div><p>这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。</p> + 业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题:

      +
      请求接口,按返回包字段判断请求成功与否。
      +
      +

      伪代码如下:

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code int    `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 场景1,返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +    // 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	// 场景2,返回包不符合接口要求,缺少相关字段
      +	str = `{"retCode":100,"retMsg":"failed"}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +    // 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +}
      +

      这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。

      +

      缺少code字段,Unmarshal解析后,按默认值处理,所以code为0,导致验证出错。

      +

      修正方案:

      +

      Code字段定义为引用类型,通过判断地址是否为nil来区分缺少字段的情况。

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code *int   `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +	// 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	if res.Code == nil || *res.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +
      +	// 返回包不符合接口要求,缺少相关字段
      +	str = `{"id":1}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +	// 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +
      +	if res1.Code == nil || *res1.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +}
      +

      参考资料:

      +

      how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided

      +

      json-key-not-set-null-golang

      +]]>
      +
      + + 疫情下的生活 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220520-192500@2x.png" width="1442" height="924" alt="20220520-192500@2x" title="" loading="lazy" /> + </picture> + +</p> +<p>不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。</p> +<p>昨天看新闻,基本没有社会面新增了,感觉要解封了。</p> +<p>没想到今天直接被打脸,封控升级了。</p> + + +20220520-192500@2x + + +

      +

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。

      +

      昨天看新闻,基本没有社会面新增了,感觉要解封了。

      +

      没想到今天直接被打脸,封控升级了。

      +

      居家办公已经快两周了,也不知道这种日子还要多久。

      +

      在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。

      +

      媳妇帮我想了个办法,投屏到电视上。

      +

      + +20220520194127 + + +

      +

      居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。

      +

      不知道这波还要多久。

      +]]>
      +
      + + 整理下博客的一些调整 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + <p>新域名上线一段时间了,通过<code>Google Search Console</code>发现了一些问题,整理下最近进行的一些调整。</p> +<ol> +<li> +<p>更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。</p> + 新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。

      +
        +
      1. +

        更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。

        +

        通过调整Github Actions命令解决:

        +
        - name: Checkout repository
        +    uses: actions/checkout@v2
        +  - name: Checkout submodules
        +    run: git submodule update --init --recursive --remote
        +
      2. +
      3. +

        修正404页面不生效的问题 +主题是自带了404.html文件的,但是部署后没有生成对应文件。

        +

        修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。

        +

        可参考文件代码

        +
      4. +
      5. +

        两个域名导致的页面权重问题 +发现有些页面liudon.xyz收录后,liudon.com就不再收录。

        +

        为了规避这种收录问题,将liudon.xyz直接301到了liudon.com上。

        +
      6. +
      +

      目前已调整完毕,观察后续收录情况。

      +]]>
      +
      + + 疫情下的五一假期 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + Thu, 05 May 2022 20:22:23 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + <p>五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。</p> +<p>当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。</p> +<p>说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。</p> + 五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。

      +

      当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。

      +

      说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。

      +

      毕竟老话说的好,手中有粮,心里不慌。

      +

      第二天出去做核酸,特意去了趟超市,想着再买点肉,结果超市也是各种采购,东西都没了,空手而归。

      +

      工作上五一前有两个大版本的功能要发布,提前1周需求才提,节前这一周忙的要死,还赶上了疫情,好在赶在发布截止日总算发出去了。

      +

      就五一当天带娃去了家旁边的公园遛了下,这次轮滑滑的不错,一直玩到晚上7点才回家。

      +

      夏天到了,北京的杨絮、柳絮各种满天飞,出门真是受罪,其余时间都在家呆着了,偶尔下楼在小区玩会。

      +

      这个假期基本上每天核酸,感觉以后要常态化核酸了。

      +

      老家在假期里突然也疫情又起,县城整个封控了,不过老爸还是回到家了,过程稍微费了点劲。

      +

      突然发现今年的疫情貌似就没消停过,去年五一假期的时候在老家烧烤,今年还不知道什么时候能回家。

      +

      这疫情什么时候是个头啊……

      +]]>
      +
      + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      + + 二刷百望山 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + Sun, 17 Apr 2022 22:57:41 +0800 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + <p>又是周末,娃约了小伙伴一起爬山。</p> +<p>百望山,二刷走起。</p> +<p>约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。</p> +<p>出门晚了,还打不到车,快10点才到。</p> +<p>小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。</p> + 又是周末,娃约了小伙伴一起爬山。

      +

      百望山,二刷走起。

      +

      约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。

      +

      出门晚了,还打不到车,快10点才到。

      +

      小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。

      +

      继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。

      +

      这次来,山上明显绿了,花开的更多了,人比上次少多了。

      +

      下山后,又一起吃了个饭,自助吃的有点撑。

      +

      今天天气真好,视野相当不错,就是太晒了。

      +

      + +俯瞰北京 + + +

      +

      + +不知名的树 + + +

      +

      + +山顶 + + +

      +]]>
      +
      + + 带娃游颐和园 + https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/ + Mon, 11 Apr 2022 00:37:55 +0800 + https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/ + <p>上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。</p> +<p>周六7点准时起床,得早点去省得人多排队。</p> +<p>8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。</p> + 上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。

      +

      周六7点准时起床,得早点去省得人多排队。

      +

      8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。

      +

      路上不堵,40多分钟到站。

      +

      进园先去码头排队,人不太多,等了有十几分钟,终于轮到了。

      +

      总算开上了船,不过天气不太好,灰蒙蒙的。

      +

      + +终于坐上了船 + + +

      +

      水上荡了1个小时,下船向佛香阁进发。

      +

      + +佛香阁 + + +

      +

      一路向上,终于登顶,园内风景一览无余。

      +

      下来后,又去逛了十七孔桥,逛了下湖心小岛。

      +

      回家又特意坐的双层公交,第一排观光区,哈哈。

      +

      一天下来1w多步,娃估计得更多,到家直接累摊。

      +

      + +这是鸳鸯吧 + + +

      +

      + +佛香阁 + + +

      +

      + +十七孔桥 + + +

      +]]>
      +
      + + 博客架构说明 + https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/ + Sun, 10 Apr 2022 20:41:57 +0800 + https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/ + <p>在拿到<code>liudon.com</code>域名前,手中已有两个域名:</p> +<ul> +<li>liudon.org</li> +<li>liudon.xyz</li> +</ul> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu10108568587195088134.webp 653w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84_hu6151068063620748445.png 653w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="%e5%8d%9a%e5%ae%a2%e6%9e%b6%e6%9e%84.png" width="653" height="206" alt="两套域名说明" title="" loading="lazy" /> + </picture> + +</p> +<p><code>liudon.org</code>已经不再更新,仅作归档使用。 +<code>liudon.xyz</code>当时是静态博客流行,尝鲜使用。</p> + 在拿到liudon.com域名前,手中已有两个域名:

      +
        +
      • liudon.org
      • +
      • liudon.xyz
      • +
      +

      + +两套域名说明 + + +

      +

      liudon.org已经不再更新,仅作归档使用。 +liudon.xyz当时是静态博客流行,尝鲜使用。

      +

      拿到liudon.com域名后,怎么部署博客成了个问题。

      +

      因为github pages只能绑定一个自定义域名,当然可以通过创建另外一个项目,实现两套域名,但是同一个博客两个项目总感觉不太优雅。

      +

      经过一番资料查找,终于有了下面这套方案。

      +

      + +博客构建流程 + + +

      +

      通过github actionsnetlify 部署了两套自动化部署方案:

      +
        +
      • github actions部署到github pages,绑定自定义域名liudon.com
      • +
      • netlify部署到ipfs,通过cloudfare ipfs gateway解析到ipfs资源,绑定自定义域名liudon.xyz
      • +
      +

      这里要说明一下,ipfs目前访问延迟较大,这里仅作尝鲜使用。

      +

      hugoconfig.toml定义了网站域名,这里为了区分两套域名,在netlify部署时,对配置文件做了修改,保证两套域名访问各自页面,具体可参考github文件内容

      +]]>
      +
      + + 难得的清明假期 + https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/ + Wed, 06 Apr 2022 08:06:19 +0800 + https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/ + <p>前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。</p> +<p>趁着这次难得的假期,外出放松一下。</p> +<ol> +<li> +<p>爬百望山。</p> +<p>娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。</p> + 前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。

      +

      趁着这次难得的假期,外出放松一下。

      +
        +
      1. +

        爬百望山。

        +

        娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。

        +

        进园后,选择大路往上爬,中间走了一段山间台阶路。

        +

        一路走走停停,补充能量,估计有半个小时,我们就登顶了。

        +

        山上风有点大,视野不错,可以直接看到互联网的核心公司。

        +
      2. +
      3. +

        游北海公园

        +

        本来是想着去公园划船,结果到了之后发现排队的人太多了,于是放弃,改为游白塔。

        +

        我们是从北门进的园,然后一路向西去码头打算划船,绕着园子继续往南走。

        +

        一路走到了南门,心想这出去了还咋去白塔,难道要再走回去?

        +

        问了门口保安,说是可以出去往东走,从南门再进去。

        +

        进去后,要爬好几层楼梯才能上到白塔平台,一览园内风景。

        +

        + +北海白塔 + + +

        +
      4. +
      +

      希望疫情尽快散去,恢复到正常的生活。

      +]]>
      +
      + + 十一年的等待,终于拿到了liudon.com域名 + https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/ + Fri, 01 Apr 2022 01:24:05 +0800 + https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/ + <p>在<a href="https://liudon.com/about">关于</a>部分,有写域名的来历。</p> +<p>当时<code>liudon.com</code>已经被注册,所以只好注册了<code>liudon.org</code>。</p> +<p>2011年注册的<code>liudon.org</code>,最早用<code>wordpress</code>搭建了博客。</p> + 关于部分,有写域名的来历。

      +

      当时liudon.com已经被注册,所以只好注册了liudon.org

      +

      2011年注册的liudon.org,最早用wordpress搭建了博客。

      +

      这是当时的第一篇文章,Hello world

      +

      从2011年,到2019年,中间断断续续的在更新着,博客程序也从wordpress转到了typecho,再后来开始流行静态页博客,又转到了hugo下。

      +

      在这中间,一直想要拿到liudon.com域名。

      +

      看网站内容,应该是个德国乐队的网站,这里能看到网站最后的信息

      +

      3月10日的时候,偶然看到群里有在说域名,于是又搜索了一番,发现域名上了阿里云域名拍卖,购买的时候提示卖家已下家。

      +

      到国外域名商上查了一下,godaddy提供代购,name提示为高级域名,可以购买。

      +

      archive.org上的信息,2021年11月的时候服务不可用了,不知道发生了什么。

      +

      机不可失,时不再来,立马下单。

      +

      接下来就是漫长的等待了,系统显示需要5-10个工作日才能完成交易。

      +

      同时我发现他们服务条款写着,高级域名不支持退款,whois信息一直不变,担心打水漂。

      +

      期间有一天晚上11点联系客户,说是已经在加快处理。

      +

      过了10天后,交易还没完成,有点放弃了。

      +

      3月24日晚上,再次查whois,发现信息有变,登录name,发现订单已完成。

      +

      现在这个域名终于属于我了。

      +
      liudon.org 老站,备份,不再更新
      +liudon.com -> github pages
      +liudon.xyz -> netfily -> ipfs
      +
      ]]>
      +
      + + 被隔离的一周 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + Fri, 01 Apr 2022 01:20:39 +0800 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + <p>从没有想过疫情会离自己这么近,记录一下。</p> +<p>周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。</p> +<p>周二早上全员核酸阴性,继续到公司上班。</p> + 从没有想过疫情会离自己这么近,记录一下。

      +

      周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。

      +

      周二早上全员核酸阴性,继续到公司上班。

      +

      晚上8点10分刚和同事上地铁,部门群里有人说我们楼有人确诊,过了10分钟,说是大楼管控了,不让出楼了。

      +

      封在楼里的同学统一做核酸,结果出来后才能回家,好多人在公司过了夜。

      +

      周三到公司集体核酸,做完继续居家办公,今天开始公安局、社区开始联系确认信息,公司要求所有人员居家办公,社区反馈需要居家隔离。

      +

      头几天还说对门上封条了,没成想这次轮到自己了。

      +

      到03/31日,隔离解除,健康宝恢复正常。

      +

      第一次体验到健康宝显示弹窗,然后又显示居家隔离。

      +

      第一次体验到被贴封条上门磁。

      +

      第一次体验到鼻拭子,尤其解封前最后一次核酸,双鼻孔。

      +]]>
      +
      + + mysql中字符串和整型自动转换的问题 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + Mon, 14 Dec 2020 18:47:29 +0800 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + <p>表结构如下</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>desc info; +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> Field <span style="color:#f92672">|</span> Type <span style="color:#f92672">|</span> Null <span style="color:#f92672">|</span> Key <span style="color:#f92672">|</span> Default <span style="color:#f92672">|</span> Extra <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> id <span style="color:#f92672">|</span> <span style="color:#a6e22e">int</span>(<span style="color:#ae81ff">8</span>) unsigned <span style="color:#f92672">|</span> NO <span style="color:#f92672">|</span> PRI <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> auto_increment <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> name <span style="color:#f92672">|</span> varchar(<span style="color:#ae81ff">20</span>) <span style="color:#f92672">|</span> YES <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span> rows <span style="color:#f92672">in</span> set (<span style="color:#ae81ff">0.00</span> sec) +</span></span></code></pre></div><p>执行sql.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;xxx&#39;); +</span></span><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;yyy&#39;); +</span></span></code></pre></div><p>查询记录.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>select * from info; +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| id | name | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| 1 | xxx | +</span></span><span style="display:flex;"><span>| 2 | yyy | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>2 rows in set (0.00 sec) +</span></span></code></pre></div><p>执行下面sql.</p> + 表结构如下

      +
      desc info;
      ++-------+-----------------+------+-----+---------+----------------+
      +| Field | Type            | Null | Key | Default | Extra          |
      ++-------+-----------------+------+-----+---------+----------------+
      +| id    | int(8) unsigned | NO   | PRI | NULL    | auto_increment |
      +| name  | varchar(20)     | YES  |     | NULL    |                |
      ++-------+-----------------+------+-----+---------+----------------+
      +2 rows in set (0.00 sec)
      +

      执行sql.

      +
      insert into info values ('', 'xxx');
      +insert into info values ('', 'yyy');
      +

      查询记录.

      +
      select * from info;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      +|  2 | yyy  |
      ++----+------+
      +2 rows in set (0.00 sec)
      +

      执行下面sql.

      +
      select * from info where id = 1;
      +
      +select * from info where id = '1aaaa';
      +

      你先想想结果会是什么。

      +
      select * from info where id = 1;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set (0.00 sec)
      +
      +select * from info where id = '1aaaa';
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set, 1 warning (0.01 sec)
      +

      两个sql都查到了id = 1的记录,唯一的区别在于第二个sql有一个warning错误。

      +
      show warnings;
      ++---------+------+-------------------------------------------+
      +| Level   | Code | Message                                   |
      ++---------+------+-------------------------------------------+
      +| Warning | 1292 | Truncated incorrect DOUBLE value: '1aaaa' |
      ++---------+------+-------------------------------------------+
      +1 row in set (0.00 sec)
      +

      mysql在查询时,会根据字段类型进行转换,这里1aaaa被转为了1

      +]]>
      +
      + + 一次惊心动魄的Mysql更新操作 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + Tue, 19 May 2020 17:16:53 +0800 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + <h4 id="问题描述">问题描述</h4> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span># 表结构 +</span></span><span style="display:flex;"><span>MySQL &gt; desc user_packages; +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| Field | Type | Null | Key | Default | Extra | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | +</span></span><span style="display:flex;"><span>| start_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| end_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>| up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>MySQL &gt; select * from user_packages limit 5; +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| up_id | start_date | end_date | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| 185 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 186 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 187 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 188 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 189 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span></code></pre></div><h4 id="操作过程">操作过程</h4> +<p>需要更新某条记录的<code>end_date</code>字段,执行sql如下:</p> + 问题描述 +
      # 表结构
      +MySQL > desc user_packages;
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| Field          | Type                | Null | Key | Default             | Extra          |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| up_id          | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
      +| start_date     | date                | NO   |     | NULL                |                |
      +| end_date       | date                | NO   |     | NULL                |                |
      +| up_created     | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
      +| up_updated     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +5 rows in set (0.00 sec)
      +
      +MySQL > select * from user_packages limit 5;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 2018-06-30 |
      +|   186 | 2018-04-01 | 2018-06-30 |
      +|   187 | 2018-04-01 | 2018-06-30 |
      +|   188 | 2018-04-01 | 2018-06-30 |
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +5 rows in set (0.00 sec)
      +

      操作过程

      +

      需要更新某条记录的end_date字段,执行sql如下:

      +
      MySQL > update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +Query OK, 1 row affected, 1 warning (0.00 sec)
      +Rows matched: 1  Changed: 1  Warnings: 1
      +

      执行完,发现sql写错了!!!!

      +

      正确的sql应该是:

      +
      update user_packages set end_date = '2020-06-06' where up_id = 189 limit 1;
      +

      误把where写成了and,还好指定了limit = 1,只操作了一条记录。

      +

      回滚

      +

      回滚的前提,要先找到更新的那条记录。

      +

      up_id为表的主键,更新前表里已经有这条记录了,主键不能重复,感觉语句应该没有执行成功。

      +
      MySQL > select * from user_packages where up_id = 189;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      执行查询语句,表里确实也只有这一条up_id=189的记录。

      +

      感觉这个update语句应该没执行成功,但是没执行成功应该报错的呀。

      +

      这个时候把希望放到了语句结果里的Warnings: 1,是不是没执行成功呢。

      +

      因为紧接着又执行了其他语句,所以也无法通过show warnings查看具体的错误信息了。

      +

      那么这条语句到底执行成功了吗?如果执行成功,那么修改的是哪条记录呢?

      +

      这里通过一番查找,终于定位到了记录。

      +

      AND分隔符,在mysql语句里优先级最高。

      +
      update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +
      +等效为
      +
      +update user_packages set end_date = ('2020-06-06' and up_id = 189) limit 1;
      +
      +即
      +
      +update user_packages set end_date = 0 limit 1;
      +

      因为end_date字段为date类型,所以写入表后的记录为0000-00-00

      +
      MySQL > select * from user_packages where end_date = '0000-00-00';
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 0000-00-00 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      好在这次只更新了一条记录,否则后果无法想象。

      +

      切记不要在现网直接操作DB。

      +

      相关资料:

      +

      一个我认为是bug的UPDATE语句

      +]]>
      +
      + + 如何在北京公积金网站上修改婚姻状况 + https://liudon.com/posts/how-to-modify-marital-status/ + Fri, 17 Jan 2020 17:14:32 +0800 + https://liudon.com/posts/how-to-modify-marital-status/ + <blockquote> +<p>关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告</p> +<p>时间:2020年01月08日</p> +<p>来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html</p> + +

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告

      +

      时间:2020年01月08日

      +

      来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html

      + +

      1月8日,北京公积金中心发文,从1月10日开始可以网上办理公积金提取了。

      +

      这里单独讲一下外地领证的情况下,如何修改婚姻状况。

      +
        +
      1. +

        进入提取页面,默认显示为未说明的婚姻状况。

        +

        + +QQ截图20200117170836.jpg + + +

        +
      2. +
      3. +

        点击婚姻状况,选择已婚。

        +

        + +QQ截图20200117170917.jpg + + +

        +

        可以看到婚姻状态相关的输入框都为灰色,不可修改。

        +
      4. +
      5. +

        点击婚姻信息修改按钮,会弹出一个民政校验的弹窗,因为我是外地领证,这里查不到信息。

        +

        + +QQ截图20200117171149.jpg + + +

        +

        注意图片右下角还是只有一个婚姻信息修改按钮。

        +
      6. +
      7. +

        点击弹窗里的确认按钮。

        +

        + +QQ截图20200117171236.jpg + + +

        +

        这下婚姻状况相关的输入框都可以填写了。

        +

        另外图片右下角里多了一个上传结婚证按钮。

        +
      8. +
      9. +

        填写完信息后,点击上传结婚证按钮。

        +

        + +QQ截图20200117171252.jpg + + +

        +
      10. +
      11. +

        按说明上传两张结婚证照片,点击确认即可。

        +
      12. +
      13. +

        其余的按公积金官网文档操作,最后提交即可。

        +
      14. +
      +

      关于提取时间,我是前一天中午申请,第二天下午就到账了,效率还是很棒的。

      +]]>
      +
      + + PHP7.2编译安装后没有php.ini文件的问题 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + Tue, 26 Nov 2019 19:56:18 +0800 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + <p>下载PHP7.2源码,编译安装。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v +</span></span><span style="display:flex;"><span>PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) +</span></span><span style="display:flex;"><span>Copyright (c) 1997-2018 The PHP Group +</span></span><span style="display:flex;"><span>Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies +</span></span><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# +</span></span></code></pre></div><p>安装Swoole。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>phpize &amp;&amp; \ +</span></span><span style="display:flex;"><span>./configure &amp;&amp; \ +</span></span><span style="display:flex;"><span>make &amp;&amp; make install +</span></span></code></pre></div><p>安装完,准备修改<code>php.ini</code>文件,结果没找到。</p> + 下载PHP7.2源码,编译安装。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v
      +PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )
      +Copyright (c) 1997-2018 The PHP Group
      +Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]# 
      +

      安装Swoole。

      +
      phpize && \
      +./configure && \
      +make && make install
      +

      安装完,准备修改php.ini文件,结果没找到。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll /usr/local/services/php/etc/
      +total 88
      +-rw-r--r-- 1 root root  1364 Nov 26 19:34 pear.conf
      +-rw-r--r-- 1 root root  4508 Nov 26 19:34 php-fpm.conf.default
      +drwxr-xr-x 2 root root  4096 Nov 26 19:34 php-fpm.d
      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => (none)
      +

      这是什么鬼,居然没有php.ini文件。

      +

      原来PHP源码里提供了两个php.ini文件,你需要按需拷贝到你的PHP的目录下。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll ../php-7.2.25 | grep ini
      +-rw-rw-r--  1 root root   71232 Nov 20 23:11 php.ini-development
      +-rw-rw-r--  1 root root   71413 Nov 20 23:11 php.ini-production
      +

      拷贝后。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => /usr/local/services/php/etc/php.ini
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]#
      +
      ]]>
      +
      + + 检测网站支持的SSL/TLS协议版本 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + <blockquote> +<p>Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。</p> +</blockquote> +<p>为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。</p> + +

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。

      + +

      为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。

      +
        +
      1. +

        检测是否支持TLSv1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1
        +
      2. +
      3. +

        检测是否支持TLSv1.1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_1
        +
      4. +
      5. +

        检测是否支持TLSv1.2

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_2
        +
      6. +
      +

      参考资料:How to check what SSL/TLS versions are available for a website?

      +]]>
      +
      + + 记一次难忘的手术经历 + https://liudon.com/posts/an-unforgettable-surgical-experience/ + Mon, 28 Oct 2019 19:03:31 +0800 + https://liudon.com/posts/an-unforgettable-surgical-experience/ + 有痔青年的手术经历 + 俗话说的好,十人九痔。这九个人里就有我一个。 😂

      +

      去年因为痔疮去过一趟医院,医生当时建议手术。

      +

      后来用了点药,没啥症状了,就不放在心上了。

      +

      结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。

      +

      第二天赶紧上医院检查,先上开塞露通便,通完舒服多了。

      +

      医生检查完,让住院手术,这次狠了狠心,手术做掉,一了百了。

      +
        +
      1. 10/15住院
      2. +
      3. 10/16手术
      4. +
      5. 10/22出院
      6. +
      +

      上一次手术是在10年前了,全麻,什么都不知道。

      +

      这次是局麻,上手术台后,医生往腰上打了一针,很疼。

      +

      过了10分钟左右,肛门已经使不上劲了,开始手术。

      +

      过程一直感觉有往里打气,东西插进去。

      +

      医生说有用镇静,后来越来越困,感觉要晕过去,不过还是撑着没睡。

      +

      出来后,整个下午下半身都是麻的,没啥精神,事后就记得媳妇坐在我对面。

      +

      术后6个小时内要小便,不然就得插尿管。

      +

      第二天开始喝流食,然后就是每天医生给换药。

      +

      第三天开始大便,伤口那个痛啊。 😭😭😭

      +

      后来学会在热水里泡着拉,感觉舒服了点。

      +

      每天都要担心大便,担心不拉,或者担心拉的太多。

      +

      出院后第二天,早上大便一盆血,以为是大出血,把媳妇叫回来直奔医院。

      +

      到医院病房,主治医生不在,找了个其他医生给检查,说是有点出血。

      +

      检查的时候塞了个肛门镜,这玩意实在是痛苦。

      +

      然后说病房没工具,上门诊处理吧。然后我就拖着身子,走到门诊。

      +

      主治医生说没啥大事,其实可以不处理,不过你也是来医院了,还是处理一下吧。

      +

      处理的时候,又塞了个肛门镜,然后往屁眼上打了三针封闭,想死的心都有了。

      +

      回家后,继续卧床休息,每天换药。

      +

      朋友们,请一定要好好对你的🌻。

      +]]>
      +
      + + 十一假期经历 + https://liudon.com/posts/chinese-national-day/ + Tue, 08 Oct 2019 13:13:36 +0800 + https://liudon.com/posts/chinese-national-day/ + <p>今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。</p> +<p>只好请了2天假,提前回家了,给自己也放个假休息一下。</p> +<hr> +<p>回家的几个经历:</p> +<ol> +<li> +<p>家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。</p> + 今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。

      +

      只好请了2天假,提前回家了,给自己也放个假休息一下。

      +
      +

      回家的几个经历:

      +
        +
      1. +

        家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。

        +

        + +64F68D7789A8D877A41E6D694ABE5444.png + + +

        +
      2. +
      3. +

        村里今年也要通天然气了,各家各户都要拆煤炉,装壁挂炉,希望天然气供应不出问题。

        +
      4. +
      5. +

        全家一起去了趟家门口的园博园,希望以后可以走的更远一些。

        +

        + +F69E19D28B34C163878F2A6E1A43E039.png + + +

        +
      6. +
      7. +

        娃确实是长大了,有了自己的想法,有自己的脾气了。

        +
        +

        背景:晚上开着灯,要睡觉了。

        +

        我:睡觉吧。

        +

        娃:爸爸,你说开着灯能睡觉吗?

        +

        我:不能吧…(不知道她为啥问这个问题)

        +

        娃:那你开着灯让我睡觉,我怎么睡呀!

        +

        我:原来你在这里等着我呢啊…

        +
        +
      8. +
      +
      +

      接下来,就努力工作吧。

      +]]>
      +
      + + Swoft 框架运行分析(五) —— ConsoleProcessor模块分析 + https://liudon.com/posts/swoft-console-processor-analysis/ + Thu, 26 Sep 2019 13:14:23 +0800 + https://liudon.com/posts/swoft-console-processor-analysis/ + <blockquote> +<p>这里以Swoft启动http server为例。</p> +<p>php bin/swoft http:start</p> +<p>执行上述命令,启动http server。</p> +</blockquote> +<p>在前面第一篇文章的时候,提到了如何启动http服务。</p> +<p>今天我们就来看一下http服务是如何启动的,具体实现就在<code>ConsoleProcess</code>这个模块。</p> + +

      这里以Swoft启动http server为例。

      +

      php bin/swoft http:start

      +

      执行上述命令,启动http server。

      + +

      在前面第一篇文章的时候,提到了如何启动http服务。

      +

      今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。

      +
      /**
      +    * Handle console
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeConsole()) {
      +        return false;
      +    }
      +
      +    /** @var Router $router */
      +    $router = bean('cliRouter');
      +
      +    // Register console routes
      +    CommandRegister::register($router);
      +
      +    CLog::info(
      +        'Console command route registered (group %d, command %d)',
      +        $router->groupCount(),
      +        $router->count()
      +    );
      +
      +    // Run console application
      +    bean('cliApp')->run();
      +
      +    return $this->application->afterConsole();
      +}
      +

      这里调用了bean方法获取Bean实例,定义见swoft-component-2.0.5\src\bean\src\Helper\Functions.php

      +
      if (!function_exists('bean')) {
      +    /**
      +     * Get bean by name
      +     *
      +     * @param string $name Bean name Or alias Or class name
      +     *
      +     * @return object|string|mixed
      +     */
      +    function bean(string $name)
      +    {
      +        if (BeanFactory::isSingleton('config')) {
      +            return BeanFactory::getBean($name);
      +        }
      +
      +        return sprintf('${%s}', $name);
      +    }
      +}
      +

      这里调用了BeanFactorygetBean方法。

      +
      /**
      +    * Get object by name
      +    *
      +    * @param string $name Bean name Or alias Or class name
      +    *
      +    * @return object|mixed
      +    */
      +public static function getBean(string $name)
      +{
      +    return Container::getInstance()->get($name);
      +}
      +

      最终调用的是Swoft\Bean\Container下的get方法。

      +
      /**
      +    * Finds an entry of the container by its identifier and returns it.
      +    *
      +    * @param string $id Bean name Or alias Or class name
      +    *
      +    * When class name will return all of instance for class name
      +    *
      +    * @return object
      +    * @throws InvalidArgumentException
      +    */
      +public function get($id)
      +{
      +    // It is singleton
      +    if (isset($this->singletonPool[$id])) {
      +        return $this->singletonPool[$id];
      +    }
      +
      +    // Prototype by clone
      +    if (isset($this->prototypePool[$id])) {
      +        return clone $this->prototypePool[$id];
      +    }
      +
      +    // Alias name
      +    $aliasId = $this->aliases[$id] ?? '';
      +    if ($aliasId) {
      +        return $this->get($aliasId);
      +    }
      +
      +    // Class name
      +    $classNames = $this->classNames[$id] ?? [];
      +    if ($classNames) {
      +        $id = end($classNames);
      +        return $this->get($id);
      +    }
      +
      +    // Interface
      +    if (interface_exists($id)) {
      +        $id = InterfaceRegister::getInterfaceInjectBean($id);
      +        return $this->get($id);
      +    }
      +
      +    // Not defined
      +    if (!isset($this->objectDefinitions[$id])) {
      +        throw new InvalidArgumentException(sprintf('The bean of %s is not defined', $id));
      +    }
      +
      +    /* @var ObjectDefinition $objectDefinition */
      +    $objectDefinition = $this->objectDefinitions[$id];
      +
      +    // Prototype
      +    return $this->safeNewBean($objectDefinition->getName());
      +}
      +

      获取对应的ObjectDefinition实例,然后调用safeNewBean方法。

      +
      /**
      +    * Secure creation of beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object|mixed
      +    */
      +private function safeNewBean(string $beanName, string $id = '')
      +{
      +    try {
      +        return $this->newBean($beanName, $id);
      +    } catch (Throwable $e) {
      +        throw new InvalidArgumentException($e->getMessage(), 500, $e);
      +    }
      +}
      +

      这里又调用了newBean方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的Bean类。

      +

      cliRouter对应的类是说明呢?这个定义在swoft-component-2.0.5\src\console\src\AutoLoader.php里。

      +
      /**
      +    * {@inheritDoc}
      +    */
      +public function beans(): array
      +{
      +    return [
      +        'cliApp'    => [
      +            'class'   => Application::class,
      +            'version' => '2.0.0'
      +        ],
      +        'cliRouter' => [
      +            'class' => Router::class,
      +        ],
      +        'cliDispatcher' => [
      +            'class' => ConsoleDispatcher::class,
      +        ],
      +    ];
      +}
      +

      所以$router = bean('cliRouter'),返回的是一个Swoft\Console\Router\Router类。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CommandRegister::register($router);
      +

      调用了CommandRegister类的register方法。

      +
      
      +/**
      +    * @param Router $router
      +    * @throws ReflectionException
      +    */
      +public static function register(Router $router): void
      +{
      +    $maxLen  = 12;
      +    $groups  = [];
      +    $docOpts = [
      +        'allow' => ['example']
      +    ];
      +    $defInfo = [
      +        'example'     => '',
      +        'description' => 'No description message',
      +    ];
      +
      +    foreach (self::$commands as $class => $mapping) {
      +        $names = [];
      +        $group = $mapping['group'];
      +        // Set ID aliases
      +        $router->setIdAliases($mapping['idAliases']);
      +        // Set group name aliases
      +        $router->setGroupAliases($group, $mapping['aliases']);
      +
      +        $refInfo = Swoft::getReflection($class);
      +        $mhdInfo = $refInfo['methods'] ?? [];
      +        $grpOpts = $mapping['options'] ?? [];
      +
      +        foreach ($mapping['commands'] as $method => $route) {
      +            // $method = $route['method'];
      +            $cmdDesc = $route['desc'];
      +            $command = $route['command'];
      +
      +            $idLen = strlen($group . $command);
      +            if ($idLen > $maxLen) {
      +                $maxLen = $idLen;
      +            }
      +
      +            $cmdExam = '';
      +            if (!empty($mhdInfo[$method]['comments'])) {
      +                $tagInfo = DocBlock::getTags($mhdInfo[$method]['comments'], $docOpts, $defInfo);
      +                $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo['description']);
      +                $cmdExam = $tagInfo['example'];
      +            }
      +
      +            $route['group']   = $group;
      +            $route['desc']    = ucfirst($cmdDesc);
      +            $route['example'] = $cmdExam;
      +            $route['options'] = self::mergeOptions($grpOpts, $route['options']);
      +            // Append group option
      +            $route['enabled']   = $mapping['enabled'];
      +            $route['coroutine'] = $mapping['coroutine'];
      +
      +            $router->map($group, $command, [$class, $method], $route);
      +            $names[] = $command;
      +        }
      +
      +        $groupExam = '';
      +        $groupDesc = $mapping['desc'];
      +        if (!empty($refInfo['comments'])) {
      +            $tagInfo   = DocBlock::getTags($refInfo['comments'], $docOpts, $defInfo);
      +            $groupDesc = $groupDesc ?: Str::firstLine($tagInfo['description']);
      +            $groupExam = $tagInfo['example'];
      +        }
      +
      +        $groups[$group] = [
      +            'names'   => $names,
      +            'desc'    => ucfirst($groupDesc),
      +            'class'   => $class,
      +            'alias'   => $mapping['alias'],
      +            'aliases' => $mapping['aliases'],
      +            'example' => $groupExam,
      +        ];
      +    }
      +
      +    $router->setGroups($groups);
      +    // +1 because router->delimiter
      +    $router->setKeyWidth($maxLen + 1);
      +    // clear data
      +    self::$commands = [];
      +}
      +

      这里遍历了类属性$commands注册路由。

      +

      那么$commands这个属性是哪里来的呢?

      +

      既然开头我们说的是http服务是怎么启动的,这里我们就以http-server来举例,找到swoft-component-2.0.5\src\http-server\src\Command\HttpServerCommand.php文件。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server\Command;
      +
      +use ReflectionException;
      +use Swoft;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Console\Annotation\Mapping\Command;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\Annotation\Mapping\CommandOption;
      +use Swoft\Console\Helper\Show;
      +use Swoft\Http\Server\HttpServer;
      +use Swoft\Server\Command\BaseServerCommand;
      +use Swoft\Server\Exception\ServerException;
      +use function bean;
      +use function input;
      +use function output;
      +
      +/**
      + * Provide some commands to manage the swoft HTTP server
      + *
      + * @since 2.0
      + *
      + * @Command("http", alias="httpsrv", coroutine=false)
      + * @example
      + *  {fullCmd}:start     Start the http server
      + *  {fullCmd}:stop      Stop the http server
      + */
      +class HttpServerCommand extends BaseServerCommand
      +{
      +    /**
      +     * Start the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +     * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @throws ServerException
      +     * @example
      +     *   {fullCommand}
      +     *   {fullCommand} -d
      +     *
      +     */
      +    public function start(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $masterPid = $server->getPid();
      +            output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +            return;
      +        }
      +
      +        // Startup settings
      +        $this->configStartOption($server);
      +
      +        $settings = $server->getSetting();
      +        // Setting
      +        $workerNum = $settings['worker_num'];
      +
      +        // Server startup parameters
      +        $mainHost = $server->getHost();
      +        $mainPort = $server->getPort();
      +        $modeName = $server->getModeName();
      +        $typeName = $server->getTypeName();
      +
      +        // Http
      +        $panel = [
      +            'HTTP' => [
      +                'listen' => $mainHost . ':' . $mainPort,
      +                'type'   => $typeName,
      +                'mode'   => $modeName,
      +                'worker' => $workerNum,
      +            ],
      +        ];
      +
      +        // Port Listeners
      +        $panel = $this->appendPortsToPanel($server, $panel);
      +
      +        Show::panel($panel);
      +
      +        output()->writeln('<success>HTTP server start success !</success>');
      +
      +        // Start the server
      +        $server->start();
      +    }
      +
      +    /**
      +     * Reload worker processes
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-t]")
      +     * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function reload(): void
      +    {
      +        $server = $this->createServer();
      +        $script = input()->getScript();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot reload</error>');
      +            return;
      +        }
      +
      +        output()->writef('<info>Server %s is reloading</info>', $script);
      +
      +        if ($reloadTask = input()->hasOpt('t')) {
      +            Show::notice('Will only reload task worker');
      +        }
      +
      +        if (!$server->reload($reloadTask)) {
      +            Show::error('The swoole server worker process reload fail!');
      +            return;
      +        }
      +
      +        output()->writef('<success>HTTP server %s reload success</success>', $script);
      +    }
      +
      +    /**
      +     * Stop the currently running server
      +     *
      +     * @CommandMapping()
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function stop(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot stop.</error>');
      +            return;
      +        }
      +
      +        // Do stopping.
      +        $server->stop();
      +    }
      +
      +    /**
      +     * Restart the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
      +     * @CommandOption("daemon", short="d", desc="Run server on the background")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @example
      +     *  {fullCommand}
      +     *  {fullCommand} -d
      +     */
      +    public function restart(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $success = $server->stop();
      +
      +            if (!$success) {
      +                output()->error('Stop the old server failed!');
      +                return;
      +            }
      +        }
      +
      +        output()->writef('<success>Server HTTP restart success !</success>');
      +        $server->startWithDaemonize();
      +    }
      +
      +    /**
      +     * @return HttpServer
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    private function createServer(): HttpServer
      +    {
      +        $script  = input()->getScript();
      +        $command = $this->getFullCommand();
      +
      +        /** @var HttpServer $server */
      +        $server = bean('httpServer');
      +        $server->setScriptFile(Swoft::app()->getPath($script));
      +        $server->setFullCommand($command);
      +
      +        return $server;
      +    }
      +}
      +

      通过Swoft文档,我们可以看到这里分别使用了类注解和方法注解。

      +
      @Command("http", alias="httpsrv", coroutine=false)
      +@CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +@CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +...
      +

      通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。

      +

      这里以Swoft\Console\Annotation\Mapping\CommandMapping这个注解为例,对应的注解解析类为Swoft\Console\Annotation\Parser\CommandMappingParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Console\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\CommandRegister;
      +
      +/**
      + * Class CommandMappingParser
      + *
      + * @since 2.0
      + * @AnnotationParser(CommandMapping::class)
      + */
      +class CommandMappingParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int            $type Class or Method or Property
      +     * @param CommandMapping $annotation Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_METHOD) {
      +            throw new AnnotationException('`@CommandMapping` must be defined on class method!');
      +        }
      +
      +        $method = $this->methodName;
      +
      +        // add route info for controller action
      +        CommandRegister::addRoute($this->className, $method, [
      +            'command' => $annotation->getName() ?: $method,
      +            'method'  => $method,
      +            'alias'   => $annotation->getAlias(),
      +            'aliases' => $annotation->getAliases(),
      +            'desc'    => $annotation->getDesc(),
      +            'usage'   => $annotation->getUsage(),
      +            // 'example' => $annotation->getExample(),
      +        ]);
      +
      +        return [];
      +    }
      +}
      +

      看到这里,你应该可以猜到CommandRegister类的$commands是怎么来的了吧。

      +

      我们看下CommandRegister类的addRoute方法,验证下想法。

      +
      /**
      +    * @param string $class
      +    * @param string $method
      +    * @param array  $route
      +    *
      +    * @throws AnnotationException
      +    */
      +public static function addRoute(string $class, string $method, array $route): void
      +{
      +    self::checkClass($class);
      +
      +    // init some keys
      +    $route['options']   = [];
      +    $route['arguments'] = [];
      +    // save
      +    self::$commands[$class]['commands'][$method] = $route;
      +}
      +

      bingo,跟我们猜想的一模一样,这下我们也知道CommandMapping这个注解是用来注册终端的路由信息。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CLog::info(
      +    'Console command route registered (group %d, command %d)',
      +    $router->groupCount(),
      +    $router->count()
      +);
      +

      打印日志。

      +
      // Run console application
      +bean('cliApp')->run();
      +

      感觉到了重头戏。

      +

      根据前面的代码,我们知道cliApp这个Bean实例对应的类是Swoft\Console\Application

      +
      /**
      +    * @return void
      +    * @throws ContainerException
      +    */
      +public function run(): void
      +{
      +    try {
      +        Swoft::trigger(ConsoleEvent::RUN_BEFORE, $this);
      +
      +        // Prepare
      +        $this->prepare();
      +
      +        // Get input command
      +        $inputCommand = $this->input->getCommand();
      +
      +        if (!$inputCommand) {
      +            $this->filterSpecialOption();
      +        } else {
      +            $this->doRun($inputCommand);
      +        }
      +
      +        Swoft::trigger(ConsoleEvent::RUN_AFTER, $this, $inputCommand);
      +    } catch (Throwable $e) {
      +        /** @var ConsoleErrorDispatcher $errDispatcher */
      +        $errDispatcher = BeanFactory::getSingleton(ConsoleErrorDispatcher::class);
      +
      +        // Handle request error
      +        $errDispatcher->run($e);
      +    }
      +}
      +

      通过Swoft::trigger,注册了ConsoleEvent::RUN_BEFOREConsoleEvent::RUN_AFTER两个事件。

      +
      protected function prepare(): void
      +{
      +    $this->input  = \input();
      +    $this->output = \output();
      +
      +    // load builtin comments vars
      +    $this->setCommentsVars($this->commentsVars());
      +}
      +

      prepare比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。

      +
      $inputCommand = $this->input->getCommand();
      +if (!$inputCommand) {
      +    $this->filterSpecialOption();
      +} else {
      +    $this->doRun($inputCommand);
      +}
      +

      获取终端命令行下的输入,如果有输入执行doRun方法。

      +
      /**
      +    * @param string $inputCmd
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws Throwable
      +    */
      +protected function doRun(string $inputCmd): void
      +{
      +    $output = $this->output;
      +    /* @var Router $router */
      +    $router = Swoft::getBean('cliRouter');
      +    $result = $router->match($inputCmd);
      +
      +    // Command not found
      +    if ($result[0] === Router::NOT_FOUND) {
      +        $names = $router->getAllNames();
      +        $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +        // find similar command names by similar_text()
      +        if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +            $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +        } else {
      +            $this->showApplicationHelp(false);
      +        }
      +        return;
      +    }
      +
      +    $info = $result[1];
      +
      +    // Only input a group name, display help for the group
      +    if ($result[0] === Router::ONLY_GROUP) {
      +        $this->showGroupHelp($info['group']);
      +        return;
      +    }
      +
      +    // Display help for a command
      +    if ($this->input->getSameOpt(['h', 'help'])) {
      +        $this->showCommandHelp($info);
      +        return;
      +    }
      +
      +    // Parse default options and arguments
      +    $this->bindCommandFlags($info);
      +    $this->input->setCommandId($info['cmdId']);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +
      +    // Call command handler
      +    /** @var ConsoleDispatcher $dispatcher */
      +    $dispatcher = Swoft::getSingleton('cliDispatcher');
      +    $dispatcher->dispatch($info);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, $this, $info);
      +}
      +
      $router = Swoft::getBean('cliRouter');
      +$result = $router->match($inputCmd);
      +

      获取cliRouter实例,根据输入匹配路由操作类。

      +
      /**
      +    * Match route by input command
      +    *
      +    * @param array $params [$route]
      +    *
      +    * @return array
      +    *
      +    * [
      +    *  status, info(array)
      +    * ]
      +    */
      +public function match(...$params): array
      +{
      +    $delimiter = $this->delimiter;
      +    $inputCmd  = trim($params[0], "$delimiter ");
      +    $noSepChar = strpos($inputCmd, $delimiter) === false;
      +
      +    // If use command ID alias
      +    if ($noSepChar && isset($this->idAliases[$inputCmd])) {
      +        $inputCmd = $this->idAliases[$inputCmd];
      +        // Must re-check
      +        $noSepChar = strpos($inputCmd, $delimiter) === false;
      +    }
      +
      +    if ($noSepChar && in_array($inputCmd, $this->defaultCommands, true)) {
      +        $group   = $this->defaultGroup;
      +        $command = $this->resolveCommandAlias($inputCmd);
      +
      +        // Only a group name
      +    } elseif ($noSepChar) {
      +        $group = $this->resolveGroupAlias($inputCmd);
      +
      +        if (isset($this->groups[$group])) {
      +            return [self::ONLY_GROUP, ['group' => $group]];
      +        }
      +
      +        return [self::NOT_FOUND];
      +    } else {
      +        $nameList = explode($delimiter, $inputCmd, 2);
      +
      +        if (count($nameList) === 2) {
      +            [$group, $command] = $nameList;
      +            // resolve command alias
      +            $command = $this->resolveCommandAlias($command);
      +        } else {
      +            $command = '';
      +            // $command = $this->defaultCommand;
      +            $group = $nameList[0];
      +        }
      +    }
      +
      +    $group = $this->resolveGroupAlias($group);
      +    // build command ID
      +    $commandID = $this->buildCommandID($group, $command);
      +
      +    if (isset($this->routes[$commandID])) {
      +        $info = $this->routes[$commandID];
      +        // append some info
      +        $info['cmdId'] = $commandID;
      +
      +        return [self::FOUND, $info];
      +    }
      +
      +    if ($group && isset($this->groups[$group])) {
      +        return [self::ONLY_GROUP, ['group' => $group]];
      +    }
      +
      +    return [self::NOT_FOUND];
      +}
      +

      这里会返回匹配后的路由信息。

      +

      回到doRun方法。

      +
      // Command not found
      +if ($result[0] === Router::NOT_FOUND) {
      +    $names = $router->getAllNames();
      +    $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +    // find similar command names by similar_text()
      +    if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +        $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +    } else {
      +        $this->showApplicationHelp(false);
      +    }
      +    return;
      +}
      +
      +$info = $result[1];
      +
      +// Only input a group name, display help for the group
      +if ($result[0] === Router::ONLY_GROUP) {
      +    $this->showGroupHelp($info['group']);
      +    return;
      +}
      +
      +// Display help for a command
      +if ($this->input->getSameOpt(['h', 'help'])) {
      +    $this->showCommandHelp($info);
      +    return;
      +}
      +

      根据返回的路由信息进行不同的处理。

      +
      // Parse default options and arguments
      +$this->bindCommandFlags($info);
      +$this->input->setCommandId($info['cmdId']);
      +
      +Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +

      绑定默认参数,注册ConsoleEvent::DISPATCH_BEFORE事件。

      +
      // Call command handler
      +/** @var ConsoleDispatcher $dispatcher */
      +$dispatcher = Swoft::getSingleton('cliDispatcher');
      +$dispatcher->dispatch($info);
      +

      获取cliDispatcherBean实例,对应Swoft\Console\ConsoleDispatcher类,调用dispatch方法。

      +
      /**
      +    * @param array $params
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws Throwable
      +    */
      +public function dispatch(...$params): void
      +{
      +    $route = $params[0];
      +    // Handler info
      +    [$className, $method] = $route['handler'];
      +
      +    // Bind method params
      +    $params = $this->getBindParams($className, $method);
      +    $object = Swoft::getSingleton($className);
      +
      +    // Blocking running
      +    if (!$route['coroutine']) {
      +        $this->before(get_parent_class($object), $method);
      +        PhpHelper::call([$object, $method], ...$params);
      +        $this->after($method);
      +        return;
      +    }
      +
      +    // Hook php io function
      +    Runtime::enableCoroutine();
      +
      +    // If in unit test env, has been in coroutine.
      +    if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
      +        $this->executeByCo($object, $method, $params);
      +        return;
      +    }
      +
      +    // Coroutine running
      +    srun(function () use ($object, $method, $params) {
      +        $this->executeByCo($object, $method, $params);
      +    });
      +}
      +

      获取路由对应的类和方法,通过Swoft::getSingleton($className);实例化对象。

      +

      如果未开启协程,则用PhpHelper::call([$object, $method], ...$params);调用对应的方法。

      +

      开启协程的话,使用$this->executeByCo($object, $method, $params);调用对应的方法。

      +

      我们前面启动命令是php bin/swoft http:start,这里对应的类就是Swoft\Http\Server\Command\HttpServerCommand,方法就是start

      +
      /**
      +    * Start the http server
      +    *
      +    * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +    * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +    *
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws ServerException
      +    * @example
      +    *   {fullCommand}
      +    *   {fullCommand} -d
      +    *
      +    */
      +public function start(): void
      +{
      +    $server = $this->createServer();
      +
      +    // Check if it has started
      +    if ($server->isRunning()) {
      +        $masterPid = $server->getPid();
      +        output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +        return;
      +    }
      +
      +    // Startup settings
      +    $this->configStartOption($server);
      +
      +    $settings = $server->getSetting();
      +    // Setting
      +    $workerNum = $settings['worker_num'];
      +
      +    // Server startup parameters
      +    $mainHost = $server->getHost();
      +    $mainPort = $server->getPort();
      +    $modeName = $server->getModeName();
      +    $typeName = $server->getTypeName();
      +
      +    // Http
      +    $panel = [
      +        'HTTP' => [
      +            'listen' => $mainHost . ':' . $mainPort,
      +            'type'   => $typeName,
      +            'mode'   => $modeName,
      +            'worker' => $workerNum,
      +        ],
      +    ];
      +
      +    // Port Listeners
      +    $panel = $this->appendPortsToPanel($server, $panel);
      +
      +    Show::panel($panel);
      +
      +    output()->writeln('<success>HTTP server start success !</success>');
      +
      +    // Start the server
      +    $server->start();
      +}
      +

      这里先调用了createServer方法。

      +
      /**
      +    * @return HttpServer
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +private function createServer(): HttpServer
      +{
      +    $script  = input()->getScript();
      +    $command = $this->getFullCommand();
      +
      +    /** @var HttpServer $server */
      +    $server = bean('httpServer');
      +    $server->setScriptFile(Swoft::app()->getPath($script));
      +    $server->setFullCommand($command);
      +
      +    return $server;
      +}
      +

      获取httpServerBean实例。

      +

      框架定义在swoft-component-2.0.5\src\http-server\src\AutoLoader.php,这里声明了onRequest回调事件。

      +
      'httpServer'      => [
      +    'on' => [
      +        SwooleEvent::REQUEST => bean(RequestListener::class)
      +    ]
      +],
      +

      业务定义在swoft-2.0.5\app\bean.php

      +
      'httpServer'        => [
      +    'class'    => HttpServer::class,
      +    'port'     => 18306,
      +    'listener' => [
      +        'rpc' => bean('rpcServer')
      +    ],
      +    'process'  => [
      +//            'monitor' => bean(MonitorProcess::class)
      +//            'crontab' => bean(CrontabProcess::class)
      +    ],
      +    'on'       => [
      +//            SwooleEvent::TASK   => bean(SyncTaskListener::class),  // Enable sync task
      +        SwooleEvent::TASK   => bean(TaskListener::class),  // Enable task must task and finish event
      +        SwooleEvent::FINISH => bean(FinishListener::class)
      +    ],
      +    /* @see HttpServer::$setting */
      +    'setting'  => [
      +        'task_worker_num'       => 12,
      +        'task_enable_coroutine' => true
      +    ]
      +],
      +

      createServer返回的是一个Swoft\Http\Server\HttpServer实例。

      +

      回到HttpServerCommand类的start方法。

      +
      // Start the server
      +$server->start();
      +

      调用Swoft\Http\Server\HttpServer类的start方法。

      +
      /**
      +    * Start server
      +    *
      +    * @throws ServerException
      +    * @throws ContainerException
      +    */
      +public function start(): void
      +{
      +    $this->swooleServer = new \Swoole\Http\Server($this->host, $this->port, $this->mode, $this->type);
      +    $this->startSwoole();
      +}
      +

      声明Swoole\Http\Server对象,调用startSwoole方法。

      +

      Swoft\Http\Server\HttpServer类继承自Swoft\Server\Server类,startSwoole方法定义在这个类。

      +
      /**
      +    * Bind swoole event and start swoole server
      +    *
      +    * @throws ServerException
      +    * @throws Swoft\Bean\Exception\ContainerException
      +    */
      +protected function startSwoole(): void
      +{
      +    if (!$this->swooleServer) {
      +        throw new ServerException('You must to new server before start swoole!');
      +    }
      +
      +    // Always enable coroutine hook on server
      +    CLog::info('Swoole\Runtime::enableCoroutine');
      +    Runtime::enableCoroutine();
      +
      +    Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);
      +
      +    // Set settings
      +    $this->swooleServer->set($this->setting);
      +    // Update setting property
      +    // $this->setSetting($this->swooleServer->setting);
      +
      +    // Before Add event
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);
      +
      +    // Register events
      +    $defaultEvents = $this->defaultEvents();
      +    $swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +    // Add events
      +    $this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +
      +    //After add event
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);
      +
      +    // Before listener
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);
      +
      +    // Add port listener
      +    $this->addListener();
      +
      +    // Before bind process
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);
      +
      +    // Add Process
      +    Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);
      +
      +    // After bind process
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);
      +
      +    // Trigger event
      +    Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));
      +
      +    // Storage server instance
      +    self::$server = $this;
      +
      +    // Start swoole server
      +    $this->swooleServer->start();
      +}
      +
      $this->swooleServer->set($this->setting);
      +

      设置Swoole运行配置。

      +
      // Register events
      +$defaultEvents = $this->defaultEvents();
      +$swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +// Add events
      +$this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +

      添加Swoole回调事件。

      +
      // Add port listener
      +$this->addListener();
      +

      监听端口。

      +
      // Start swoole server
      +$this->swooleServer->start();
      +

      启动Swoole\Http\Server服务。

      +

      现在服务已经启动了,那http请求是怎么被处理的呢?

      +

      这个我们下一篇再继续讲。

      +]]>
      +
      + + Swoft 框架运行分析(四) —— EventProcessor模块分析 + https://liudon.com/posts/swoft-event-processor-analysis/ + Thu, 26 Sep 2019 13:02:18 +0800 + https://liudon.com/posts/swoft-event-processor-analysis/ + <p>今天我们来看一下<code>EventProcessor</code>的实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle event register +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeEvent()) { +</span></span><span style="display:flex;"><span> CLog::warning(<span style="color:#e6db74">&#39;Stop event processor by beforeEvent return false&#39;</span>); +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/**</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> EventManager <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">=</span> bean(<span style="color:#e6db74">&#39;eventManager&#39;</span>); +</span></span><span style="display:flex;"><span> [<span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2] <span style="color:#f92672">=</span> ListenerRegister::register(<span style="color:#f92672">$</span>eventManager); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Event manager initialized(</span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> listener, </span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> subscriber)&#39;</span>, <span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> Trigger a app init event +</span></span><span style="display:flex;"><span> Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterEvent(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>获取<code>eventManager</code>的<code>Bean</code>实例,对应为<code>Swoft\Event\Manager\EventManager</code>类。</p> + 今天我们来看一下EventProcessor的实现。

      +
      /**
      +    * Handle event register
      +    * @return bool
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeEvent()) {
      +        CLog::warning('Stop event processor by beforeEvent return false');
      +        return false;
      +    }
      +
      +    /** @var EventManager $eventManager */
      +    $eventManager = bean('eventManager');
      +    [$count1, $count2] = ListenerRegister::register($eventManager);
      +
      +    CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2);
      +
      +    // Trigger a app init event
      +    Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +
      +    return $this->application->afterEvent();
      +}
      +

      获取eventManagerBean实例,对应为Swoft\Event\Manager\EventManager类。

      +
      [$count1, $count2] = ListenerRegister::register($eventManager);
      +

      调用ListenerRegister类的register方法。

      +
      /**
      +    * @param EventManager $em
      +    *
      +    * @return array
      +    */
      +public static function register(EventManager $em): array
      +{
      +    foreach (self::$listeners as $className => $eventInfo) {
      +        $listener = Swoft::getSingleton($className);
      +
      +        if (!$listener instanceof EventHandlerInterface) {
      +            throw new RuntimeException("The event listener class '{$className}' must be instanceof EventHandlerInterface");
      +        }
      +
      +        $em->addListener($listener, $eventInfo);
      +    }
      +
      +    foreach (self::$subscribers as $className) {
      +        $subscriber = Swoft::getSingleton($className);
      +        if (!$subscriber instanceof EventSubscriberInterface) {
      +            throw new RuntimeException("The event subscriber class '{$className}' must be instanceof EventSubscriberInterface");
      +        }
      +
      +        $em->addSubscriber($subscriber);
      +    }
      +
      +    $count1 = count(self::$listeners);
      +    $count2 = count(self::$subscribers);
      +    // Clear data
      +    self::$listeners = self::$subscribers = [];
      +
      +    return [$count1, $count2];
      +}
      +

      遍历ListenerRegister类下的$listeners$subscribers属性,绑定事件到eventManagerBean实例上。

      +

      这里的$listeners$subscribers是从哪里来的呢?

      +

      这里以http-server为例。

      +

      swoft-component-2.0.5\src\http-server\src\Listener目录下,存在下面三个文件。

      +
      AfterRequestListener.php
      +AppInitCompleteListener.php
      +BeforeRequestListener.php
      +

      这里我们以AppInitCompleteListener.php为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      可以看到这里通过@Listener(SwoftEvent::APP_INIT_COMPLETE),使用了Swoft\Event\Annotation\Mapping\Listener类注解,对应的注解解析类为Swoft\Event\Annotation\Parser\ListenerParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Event\Annotation\Parser;
      +
      +use Doctrine\Common\Annotations\AnnotationException;
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\ListenerRegister;
      +
      +/**
      + * Class ListenerParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(Listener::class)
      + */
      +class ListenerParser extends Parser
      +{
      +    /**
      +     * @param int      $type
      +     * @param Listener $annotation
      +     *
      +     * @return array
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@Listener` must be defined on class!');
      +        }
      +
      +        // collect listeners
      +        ListenerRegister::addListener($this->className, [
      +            // event name => listener priority
      +            $annotation->getEvent() => $annotation->getPriority()
      +        ]);
      +
      +        return [$this->className, $this->className, Bean::SINGLETON, ''];
      +    }
      +}
      +
      /**
      +    * @param string $className
      +    * @param array  $definition [event name => listener priority]
      +    */
      +public static function addListener(string $className, array $definition = []): void
      +{
      +    // Collect listeners
      +    self::$listeners[$className] = $definition;
      +}
      +

      可以看到这里通过ListenerRegister::addListener方法,往ListenerRegister上注册了$listeners属性。

      +

      属性$listeners$subscribers的值,都是通过注解解析得来。

      +

      这里我们回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      trigger的方法定义如下。

      +
      /**
      +    * Trigger an swoft application event
      +    *
      +    * @param string|EventInterface $event eg: 'app.start' 'app.stop'
      +    * @param null|mixed            $target
      +    * @param array                 $params
      +    *
      +    * @return EventInterface
      +    */
      +public static function trigger($event, $target = null, ...$params): EventInterface
      +{
      +    /** @see EventManager::trigger() */
      +    return BeanFactory::getSingleton('eventManager')->trigger($event, $target, $params);
      +}
      +

      这里调用了eventManager这个Bean实例的trigger方法。

      +
      /**
      +    * Trigger an event. Can accept an EventInterface or will create one if not passed
      +    *
      +    * @param string|EventInterface $event  'app.start' 'app.stop'
      +    * @param mixed|string          $target It is object or string.
      +    * @param array|mixed           $args
      +    *
      +    * @return EventInterface
      +    * @throws InvalidArgumentException
      +    */
      +public function trigger($event, $target = null, array $args = []): EventInterface
      +{
      +    if ($isString = is_string($event)) {
      +        $name = trim($event);
      +    } elseif ($event instanceof EventInterface) {
      +        $name = trim($event->getName());
      +    } else {
      +        throw new InvalidArgumentException('Invalid event params for trigger event handler');
      +    }
      +
      +    $shouldCall = [];
      +
      +    // Have matched listener
      +    if (isset($this->listenedEvents[$name])) {
      +        $shouldCall[$name] = '';
      +    }
      +
      +    // Like 'app.db.query' => prefix: 'app.db'
      +    if ($pos = strrpos($name, '.')) {
      +        $prefix = substr($name, 0, $pos);
      +
      +        // Have a wildcards listener. eg 'app.db.*'
      +        $wildcardEvent = $prefix . '.*';
      +        if (isset($this->listenedEvents[$wildcardEvent])) {
      +            $shouldCall[$wildcardEvent] = substr($name, $pos + 1);
      +        }
      +    }
      +
      +    // Not found listeners
      +    if (!$shouldCall) {
      +        return $isString ? $this->basicEvent : $event;
      +    }
      +
      +    /** @var EventInterface $event */
      +    if ($isString) {
      +        $event = $this->events[$name] ?? $this->basicEvent;
      +    }
      +
      +    // Initial value
      +    $event->setName($name);
      +    $event->setParams($args);
      +    $event->setTarget($target);
      +    $event->stopPropagation(false);
      +
      +    // Notify event listeners
      +    foreach ($shouldCall as $name => $method) {
      +        $this->triggerListeners($this->listeners[$name], $event, $method);
      +
      +        if ($event->isPropagationStopped()) {
      +            return $event;
      +        }
      +    }
      +
      +    // Have global wildcards '*' listener.
      +    if (isset($this->listenedEvents['*'])) {
      +        $this->triggerListeners($this->listeners['*'], $event);
      +    }
      +
      +    return $event;
      +}
      +

      如果存在对应的事件,调用triggerListeners方法。

      +
      /**
      +    * @param array|ListenerQueue $listeners
      +    * @param EventInterface      $event
      +    * @param string              $method
      +    */
      +protected function triggerListeners($listeners, EventInterface $event, string $method = ''): void
      +{
      +    // $handled = false;
      +    $name     = $event->getName();
      +    $callable = false === strpos($name, '.');
      +
      +    // 循环调用监听器,处理事件
      +    foreach ($listeners as $listener) {
      +        if ($event->isPropagationStopped()) {
      +            break;
      +        }
      +
      +        if (is_object($listener)) {
      +            if ($listener instanceof EventHandlerInterface) {
      +                $listener->handle($event);
      +            } elseif ($method && method_exists($listener, $method)) {
      +                $listener->$method($event);
      +            } elseif ($callable && method_exists($listener, $name)) {
      +                $listener->$name($event);
      +            } elseif (method_exists($listener, '__invoke')) {
      +                $listener($event);
      +            }
      +        } elseif (is_callable($listener)) {
      +            $listener($event);
      +        }
      +    }
      +}
      +

      遍历事件回调,执行对应方法。

      +

      回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      这里的事件为SwoftEvent::APP_INIT_COMPLETE,所以这里会执行这个事件下的所有回调。

      +

      这里以Swoft\Http\Server\Listener\AppInitCompleteListener为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      这里使用了Swoft\Event\Annotation\Mapping\Listener注解,对应的事件为SwoftEvent::APP_INIT_COMPLETE

      +

      按照上面的分析,这里会调用到AppInitCompleteListenerhandle方法,获取httpRouterBean实例,注册http服务的路由信息和中间件。

      +

      到这里,我们大概清楚了EventProcessor这个模块的作用,注册了所有事件的回调。

      +]]>
      +
      + + 一个git submodule update引发的问题 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + Fri, 06 Sep 2019 15:13:51 +0800 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + <h4 id="背景">背景</h4> +<p>1月份的时候,用<code>hugo</code>搭了这套博客系统。</p> +<p>本机写md文件,更新到<code>github</code>,然后通过<code>travis-ci</code>自动发布。</p> +<p>jane主题是通过<code>git submodule</code>引入的,<code>.gitmodules</code>文件内容。</p> + 背景 +

      1月份的时候,用hugo搭了这套博客系统。

      +

      本机写md文件,更新到github,然后通过travis-ci自动发布。

      +

      jane主题是通过git submodule引入的,.gitmodules文件内容。

      +
      [submodule "themes/jane"]
      +	path = themes/jane
      +	url = https://github.com/xianmin/hugo-theme-jane.git
      +

      问题

      +

      最近几天更新完文章后,发现首页显示出了问题。

      +

      一开始以为是主题有问题,具体描述见首页文章不显示了

      +

      issue里: +shaform提到使用的并不是最新的版本。 +RocFang提到是git submodule使用的问题。

      +

      但是travis-ci每次都是通过git submodule update --init --recursive更新子仓库代码的,为什么会不是最新的代码呢。

      +

      问题重现

      +

      接下来,我们用一个新的仓库,来模拟重现一下。

      +
        +
      1. +

        克隆仓库。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +[root@VM_81_18_centos test]# 
        +
      2. +
      3. +

        添加文件。

        +
         [root@VM_81_18_centos xx]# cd test/
        + [root@VM_81_18_centos test]# echo "# test" >> README.md
        + [root@VM_81_18_centos test]# git add README.md
        + [root@VM_81_18_centos test]# 
        +
      4. +
      5. +

        引用子仓库。

        +
         [root@VM_81_18_centos test]# git submodule add git@github.com:xianmin/hugo-theme-jane.git theme/jane
        + Cloning into 'theme/jane'...
        + remote: Enumerating objects: 216, done.
        + remote: Counting objects: 100% (216/216), done.
        + remote: Compressing objects: 100% (128/128), done.
        + remote: Total 6165 (delta 102), reused 159 (delta 65), pack-reused 5949
        + Receiving objects: 100% (6165/6165), 3.05 MiB | 1.70 MiB/s, done.
        + Resolving deltas: 100% (3443/3443), done.
        +
      6. +
      7. +

        查看文件列表。

        +
         [root@VM_81_18_centos test]# ll
        + total 8
        + -rw-r--r-- 1 root root    5 Sep  6 16:05 README.md
        + drwxr-xr-x 7 root root 4096 Sep  6 16:08 typecho
        + [root@VM_81_18_centos test]# 
        +
      8. +
      9. +

        查看状态。

        +
        [root@VM_81_18_centos test]# git status
        +# On branch master
        +#
        +# Initial commit
        +#
        +# Changes to be committed:
        +#   (use "git rm --cached <file>..." to unstage)
        +#
        +#	new file:   .gitmodules
        +#	new file:   README.md
        +#	new file:   typecho
        +#
        +[root@VM_81_18_centos test]# 
        +
      10. +
      11. +

        查看修改。

        +
        [root@VM_81_18_centos test]# git diff --cached
        +diff --git a/.gitmodules b/.gitmodules
        +new file mode 100644
        +index 0000000..b1ddf70
        +--- /dev/null
        ++++ b/.gitmodules
        +@@ -0,0 +1,3 @@
        ++[submodule "typecho"]
        ++       path = typecho
        ++       url = https://github.com/Liudon/typecho
        +diff --git a/README.md b/README.md
        +new file mode 100644
        +index 0000000..9daeafb
        +--- /dev/null
        ++++ b/README.md
        +@@ -0,0 +1 @@
        ++test
        +diff --git a/typecho b/typecho
        +new file mode 160000
        +index 0000000..b0c4cc7
        +--- /dev/null
        ++++ b/typecho
        +@@ -0,0 +1 @@
        ++Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +[root@VM_81_18_centos test]# 
        +

        注意最后一行Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1

        +

        这个commitId是子仓库最新提交的记录id,对应的修改记录

        +
      12. +
      13. +

        提交修改。

        +
        [root@VM_81_18_centos test]# git push -u origin master
        +Counting objects: 4, done.
        +Compressing objects: 100% (3/3), done.
        +Writing objects: 100% (4/4), 362 bytes | 0 bytes/s, done.
        +Total 4 (delta 0), reused 0 (delta 0)
        +To git@github.com:Liudon/test.git
        +* [new branch]      master -> master
        +Branch master set up to track remote branch master from origin.
        +[root@VM_81_18_centos test]# 
        +

        + + +

        +

        提交后,在github上子仓库后面会多显示一个@xxxxx,这里就是引用的commitId,对应到前面git diff最后一行。

        +

        点击查看提交记录

        +

        + + +

        +

        本次提交的commitId5b11d515db8ad8d299ef1691f115590e0015c3b7,子仓库typecho单独记录了引入时的commitId,为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,对应的提交记录

        +
      14. +
      15. +

        接下来克隆子仓库,进行更新提交。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/typecho.git
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 1, done.
        +remote: Counting objects: 100% (1/1), done.
        +remote: Total 7179 (delta 0), reused 0 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7179/7179), 7.26 MiB | 2.02 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +[root@VM_81_18_centos xx]# 
        +[root@VM_81_18_centos xx]# cd typecho/
        +[root@VM_81_18_centos typecho]# git log -n 1
        +commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +Merge: c904005 8fd7492
        +Author: 祁宁 <magike.net@gmail.com>
        +Date:   Tue Nov 18 13:59:52 2014 +0800
        +
        +    Merge branch 'master' of https://github.com/typecho/typecho
        +[root@VM_81_18_centos typecho]#
        +

        通过git log,确认最新的提交commitId为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面的引入的一致。

        +
        [root@VM_81_18_centos typecho]# echo "xxx" > test
        +[root@VM_81_18_centos typecho]# 
        +[root@VM_81_18_centos typecho]# git add test
        +[root@VM_81_18_centos typecho]# git commit -m 'test'
        +[master 5dcc8f4] test
        +1 file changed, 1 insertion(+)
        +create mode 100644 test
        +[root@VM_81_18_centos typecho]# git push
        +Counting objects: 4, done.
        +Compressing objects: 100% (2/2), done.
        +Writing objects: 100% (3/3), 252 bytes | 0 bytes/s, done.
        +Total 3 (delta 1), reused 0 (delta 0)
        +remote: Resolving deltas: 100% (1/1), completed with 1 local object.
        +To git@github.com:Liudon/typecho.git
        +b0c4cc7..5dcc8f4  master -> master
        +[root@VM_81_18_centos typecho]# 
        +

        修改文件提交。

        +
        [root@VM_81_18_centos typecho]# git log -n 1
        +commit 5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2
        +Author: liudon <i.mu@qq.com>
        +Date:   Fri Sep 6 16:26:47 2019 +0800
        +
        +    test
        +[root@VM_81_18_centos typecho]#
        +

        最新提交的commitId5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2

        +
      16. +
      17. +

        重新克隆test库。

        +
        [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
        +Receiving objects: 100% (4/4), done.
        +[root@VM_81_18_centos yy]# cd test/
        +[root@VM_81_18_centos test]# ll
        +total 8
        +-rw-r--r-- 1 root root    5 Sep  6 16:31 README.md
        +drwxr-xr-x 2 root root 4096 Sep  6 16:31 typecho
        +[root@VM_81_18_centos test]# ll typecho/
        +total 0
        +[root@VM_81_18_centos test]# 
        +

        这里可以看到typecho目录下是没有文件的。

        +
        [root@VM_81_18_centos test]# git submodule update --init --recursive
        +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.26 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +Submodule path 'typecho': checked out 'b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1'
        +[root@VM_81_18_centos test]#
        +

        更新子仓库代码,这里可以看到最终checkout的版本为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面提交时的版本一致。

        +
      18. +
      +

      问题分析

      +

      git submodule add的时候,会记录当时引入时子仓库的版本id。

      +

      git submodule update --init --recursive,会检出引入时的仓库版本,这就是为啥代码没有更新了。

      +

      问题解决

      +
      [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
      +Cloning into 'test'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
      +Receiving objects: 100% (4/4), done.
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# cd test/
      +[root@VM_81_18_centos test]# ll
      +total 8
      +-rw-r--r-- 1 root root    5 Sep  6 16:37 README.md
      +drwxr-xr-x 2 root root 4096 Sep  6 16:37 typecho
      +[root@VM_81_18_centos test]# ll typecho/
      +total 0
      +[root@VM_81_18_centos test]# 
      +[root@VM_81_18_centos test]# git submodule update --init --remote --recursive
      +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
      +Cloning into 'typecho'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
      +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.24 MiB/s, done.
      +Resolving deltas: 100% (4844/4844), done.
      +Submodule path 'typecho': checked out '5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2'
      +[root@VM_81_18_centos test]#
      +

      使用git submodule update --init --remote --recursive命令。

      +]]>
      +
      + + 一个Curl的耗时长的问题 + https://liudon.com/posts/curl-cost-time-long/ + Wed, 04 Sep 2019 11:07:46 +0800 + https://liudon.com/posts/curl-cost-time-long/ + <p>发现某个接口请求很慢,但是后端确认接口是很快的。</p> +<p>在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。</p> +<p>业务里用到了<code>Requests</code>这个库,一开始以为是这个库导致的问题。</p> + 发现某个接口请求很慢,但是后端确认接口是很快的。

      +

      在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。

      +

      业务里用到了Requests这个库,一开始以为是这个库导致的问题。

      +

      Requests_Transport_cURL类里断点定位了下,确实很慢,curl_getinfo返回的信息如下。

      +
      array (
      +  'url' => 'http://xxxxx',
      +  'content_type' => 'text/html',
      +  'http_code' => 200,
      +  'header_size' => 64,
      +  'request_size' => 305,
      +  'filetime' => -1,
      +  'ssl_verify_result' => 0,
      +  'redirect_count' => 0,
      +  'total_time' => 2.074094,
      +  'namelookup_time' => 2.5E-5,
      +  'connect_time' => 0.032107,
      +  'pretransfer_time' => 0.032109,
      +  'size_upload' => 186,
      +  'size_download' => 99,
      +  'speed_download' => 47,
      +  'speed_upload' => 89,
      +  'download_content_length' => 99,
      +  'upload_content_length' => 186,
      +  'starttransfer_time' => 2.032866,
      +  'redirect_time' => 0,
      +  'certinfo' =>
      +  array (
      +  ),
      +)
      +

      这里可以看到starttransfer_time时间很长。

      +

      搜索了一番,发现网上一个case,cURL slow starttransfer_time

      +

      里面提供了Expect: 100-continue这个header,又搜索了一番这个header资料。

      +

      curl在发POST请求的时候,如果body大于1k:

      +
        +
      1. 先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
      2. +
      3. 如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server; +如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。
      4. +
      +

      在机器上抓了个包,执行下面命令。

      +
      注意,下面port后面的80改成实际的端口
      +
      +tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
      +

      拿到的包信息。

      +
      09:17:19.421587 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306
      +E..f.m@.@...d}@.        A...XF.-.@...h....s.......
      +....T.0TPOST /cgi HTTP/1.1
      +User-Agent: php-requests/1.6
      +Accept: */*
      +Accept-Encoding: deflate, gzip
      +Referer: http://xxxxx:12345/cgi
      +Content-Length: 188
      +Expect: 100-continue
      +Content-Type: multipart/form-data; boundary=----------------------------ee2f4d848646
      +
      +
      +09:17:21.421786 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188
      +E....n@.@..Md}@.        A...XF.-.B...h....s./.....
      +....T.0[------------------------------ee2f4d848646
      +Content-Disposition: form-data; name="req"
      +
      +{"command":"zzz","appId":"yyyy"}
      +------------------------------ee2f4d848646--
      +
      +09:17:21.458628 IP xxxxx:12345 > xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117
      +E...X.@.5.Q2    A..d}@.F..X..h.-.B......3.....
      +T.2Q....HTTP/1.1 200 OK
      +Content-Type: text/html
      +Content-Length: 53
      +
      +{
      +    "data": [],
      +    "errno": 0,
      +    "error": "ok"
      +}
      +

      可以看到确实是先发了一个100-continue的请求,然后再发的实际POST请求。

      +

      在机器上执行下面的shell命令。

      +
      curl 'http://xxxxx:12345/cgi' -H"Expect: 100-continue" -v
      +

      返回如下,可以看到返回的header头里确实没有Expect这项。

      +
      * About to connect() to xxxxx port 12345 (#0)
      +*   Trying xxxxx...
      +* Connected to xxxxx (xxxxx) port 12345 (#0)
      +> GET /cloud_cgi HTTP/1.1
      +> User-Agent: curl/7.29.0
      +> Host: xxxxx:12345
      +> Accept: */*
      +> Expect: 100-continue
      +> 
      +< HTTP/1.1 200 OK
      +< Content-Type: text/html
      +< Content-Length: 42
      +< 
      +* Connection #0 to host xxxxx left intact
      +{"errno":100,"error":"参数格式错误"}
      +

      解决方法:

      +

      请求的时候,header里新增一项。

      +
      Expect:
      +
      ]]>
      +
      + + Swoft 框架运行分析(三) —— BeanProcessor模块分析 + https://liudon.com/posts/swoft-bean-processor-analysis/ + Mon, 02 Sep 2019 18:29:06 +0800 + https://liudon.com/posts/swoft-bean-processor-analysis/ + <p>今天讲一下<code>BeanProcessor</code>模块,先看一下<code>handle</code>方法实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle bean +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws ReflectionException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws AnnotationException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeBean()) { +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>handler <span style="color:#f92672">=</span> new BeanHandler(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>definitions <span style="color:#f92672">=</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>getDefinitions(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>parsers <span style="color:#f92672">=</span> AnnotationRegister::getParsers(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>annotations <span style="color:#f92672">=</span> AnnotationRegister::getAnnotations(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> BeanFactory::addDefinitions(<span style="color:#f92672">$</span>definitions); +</span></span><span style="display:flex;"><span> BeanFactory::addAnnotations(<span style="color:#f92672">$</span>annotations); +</span></span><span style="display:flex;"><span> BeanFactory::addParsers(<span style="color:#f92672">$</span>parsers); +</span></span><span style="display:flex;"><span> BeanFactory::setHandler(<span style="color:#f92672">$</span>handler); +</span></span><span style="display:flex;"><span> BeanFactory::init(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> Config <span style="color:#f92672">$</span>config<span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>config <span style="color:#f92672">=</span> BeanFactory::getBean(<span style="color:#e6db74">&#39;config&#39;</span>); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config path=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getPath()); +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config env=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getEnv()); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>stats <span style="color:#f92672">=</span> BeanFactory::getStats(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Bean is initialized(</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">)&#39;</span>, SwoftHelper::formatStats(<span style="color:#f92672">$</span>stats)); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterBean(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>先通过<code>getDefinitions</code>方法获取所有的Bean定义。</p> + 今天讲一下BeanProcessor模块,先看一下handle方法实现。

      +
      /**
      +    * Handle bean
      +    *
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws AnnotationException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeBean()) {
      +        return false;
      +    }
      +
      +    $handler     = new BeanHandler();
      +    $definitions = $this->getDefinitions();
      +    $parsers     = AnnotationRegister::getParsers();
      +    $annotations = AnnotationRegister::getAnnotations();
      +
      +    BeanFactory::addDefinitions($definitions);
      +    BeanFactory::addAnnotations($annotations);
      +    BeanFactory::addParsers($parsers);
      +    BeanFactory::setHandler($handler);
      +    BeanFactory::init();
      +
      +    /* @var Config $config*/
      +    $config = BeanFactory::getBean('config');
      +
      +    CLog::info('config path=%s', $config->getPath());
      +    CLog::info('config env=%s', $config->getEnv());
      +
      +    $stats = BeanFactory::getStats();
      +
      +    CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats));
      +
      +    return $this->application->afterBean();
      +}
      +

      先通过getDefinitions方法获取所有的Bean定义。

      +
      /**
      +    * Get bean definitions
      +    *
      +    * @return array
      +    */
      +private function getDefinitions(): array
      +{
      +    // Core beans
      +    $definitions = [];
      +    $autoLoaders = AnnotationRegister::getAutoLoaders();
      +
      +    // get disabled loaders by application
      +    $disabledLoaders = $this->application->getDisabledAutoLoaders();
      +
      +    foreach ($autoLoaders as $autoLoader) {
      +        if (!$autoLoader instanceof DefinitionInterface) {
      +            continue;
      +        }
      +
      +        $loaderClass = get_class($autoLoader);
      +
      +        // If the component is disabled by user.
      +        if (isset($disabledLoaders[$loaderClass])) {
      +            CLog::info('Auto loader(%s) is <cyan>disabled</cyan>, skip handle it', $loaderClass);
      +            continue;
      +        }
      +
      +        // If the component is not enabled.
      +        if ($autoLoader instanceof ComponentInterface && !$autoLoader->isEnable()) {
      +            continue;
      +        }
      +
      +        $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());
      +    }
      +
      +    // Bean definitions
      +    $beanFile = $this->application->getBeanFile();
      +    $beanFile = alias($beanFile);
      +
      +    if (!file_exists($beanFile)) {
      +        throw new InvalidArgumentException(
      +            sprintf('The bean config file of %s is not exist!', $beanFile)
      +        );
      +    }
      +
      +    $beanDefinitions = require $beanFile;
      +    $definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +
      +    return $definitions;
      +}
      +

      通过AnnotationRegister::getAutoLoaders()拿到所有的autoloader对象,排除掉非DefinitionInterface对象,通过bean()方法获取定义的Bean信息。

      +

      这里以http-server\src\AutoLoader.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server;
      +
      +use function bean;
      +use function dirname;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Helper\ComposerJSON;
      +use Swoft\Http\Message\ContentType;
      +use Swoft\Http\Message\Response;
      +use Swoft\Http\Server\Formatter\HtmlResponseFormatter;
      +use Swoft\Http\Server\Formatter\JsonResponseFormatter;
      +use Swoft\Http\Server\Formatter\XmlResponseFormatter;
      +use Swoft\Http\Server\Parser\JsonRequestParser;
      +use Swoft\Http\Server\Parser\XmlRequestParser;
      +use Swoft\Http\Server\Swoole\RequestListener;
      +use Swoft\Server\SwooleEvent;
      +use Swoft\SwoftComponent;
      +
      +/**
      + * Class AutoLoader
      + *
      + * @since 2.0
      + */
      +class AutoLoader extends SwoftComponent
      +{
      +    /**
      +     * Metadata information for the component.
      +     *
      +     * @return array
      +     * @see ComponentInterface::getMetadata()
      +     */
      +    public function metadata(): array
      +    {
      +        $jsonFile = dirname(__DIR__) . '/composer.json';
      +
      +        return ComposerJSON::open($jsonFile)->getMetadata();
      +    }
      +
      +    /**
      +     * Get namespace and dirs
      +     *
      +     * @return array
      +     */
      +    public function getPrefixDirs(): array
      +    {
      +        return [
      +            __NAMESPACE__ => __DIR__,
      +        ];
      +    }
      +
      +    /**
      +     * @return array
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function beans(): array
      +    {
      +        return [
      +            'httpRequest'     => [
      +                'parsers' => [
      +                    ContentType::XML  => bean(XmlRequestParser::class),
      +                    ContentType::JSON => bean(JsonRequestParser::class),
      +                ]
      +            ],
      +            'httpResponse'    => [
      +                'format'     => Response::FORMAT_JSON,
      +                'formatters' => [
      +                    Response::FORMAT_HTML => bean(HtmlResponseFormatter::class),
      +                    Response::FORMAT_JSON => bean(JsonResponseFormatter::class),
      +                    Response::FORMAT_XML  => bean(XmlResponseFormatter::class),
      +                ]
      +            ],
      +            'acceptFormatter' => [
      +                'formats' => [
      +                    ContentType::JSON => Response::FORMAT_JSON,
      +                    ContentType::HTML => Response::FORMAT_HTML,
      +                    ContentType::XML  => Response::FORMAT_XML,
      +                ]
      +            ],
      +            'httpServer'      => [
      +                'on' => [
      +                    SwooleEvent::REQUEST => bean(RequestListener::class)
      +                ]
      +            ],
      +            'httpRouter'      => [
      +                'name'            => 'swoft-http-router',
      +                // config
      +                'ignoreLastSlash' => true,
      +                'tmpCacheNumber'  => 500,
      +            ],
      +        ];
      +    }
      +}
      +

      可以看到,这里通过beans()定义了httpRequesthttpResponseacceptFormatterhttpServerhttpRouter四个Bean对象。

      +

      回到上面getDefinitions方法。

      +

      $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());

      +

      然后将Bean信息添加到definitions对象上。

      +

      之后通过$beanFile = $this->application->getBeanFile();获取bean配置文件。

      +
      $beanDefinitions = require $beanFile;
      +$definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +

      加载配置文件,然后将Bean信息添加到definitions对象上。

      +

      可以看到Bean有两种定义方式:通过AutoLoader和配置文件,与swoft官方文档里的说明一致。

      +

      回到handle方法。

      +
      $parsers     = AnnotationRegister::getParsers();
      +$annotations = AnnotationRegister::getAnnotations();
      +

      还记得上一篇文章最后提到的AnnotationRegister类的annotationsparsers两个属性吗?这里通过getParsersgetAnnotations获取这两个属性。

      +
      BeanFactory::addDefinitions($definitions);
      +BeanFactory::addAnnotations($annotations);
      +BeanFactory::addParsers($parsers);
      +BeanFactory::setHandler($handler);
      +BeanFactory::init();
      +

      向BeanFatory注册信息。

      +
      /**
      +    * Init
      +    *
      +    * @return void
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function init(): void
      +{
      +    Container::getInstance()->init();
      +}
      +
      +...
      +
      +/**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public static function addDefinitions(array $definitions): void
      +{
      +    Container::getInstance()->addDefinitions($definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public static function addAnnotations(array $annotations): void
      +{
      +    Container::getInstance()->addAnnotations($annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public static function addParsers(array $annotationParsers): void
      +{
      +    Container::getInstance()->addParsers($annotationParsers);
      +}
      +
      +/**
      +    * Set bean handler
      +    *
      +    * @param HandlerInterface $handler
      +    */
      +public static function setHandler(HandlerInterface $handler): void
      +{
      +    Container::getInstance()->setHandler($handler);
      +}
      +

      这里可以看到所有的方法,最终都调用的是Swoft\Bean\Container类。

      +
      /**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public function addDefinitions(array $definitions): void
      +{
      +    $this->definitions = ArrayHelper::merge($this->definitions, $definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public function addAnnotations(array $annotations): void
      +{
      +    $this->annotations = ArrayHelper::merge($this->annotations, $annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public function addParsers(array $annotationParsers): void
      +{
      +    $this->parsers = ArrayHelper::merge($this->parsers, $annotationParsers);
      +}
      +
      +
      +/**
      +    * @param HandlerInterface $handler
      +    */
      +public function setHandler(HandlerInterface $handler): void
      +{
      +    $this->handler = $handler;
      +}
      +

      这四个方法就是注册属性,接下来是重头戏init方法。

      +
      /**
      +    * Init
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function init(): void
      +{
      +    // Parse annotations
      +    $this->parseAnnotations();
      +
      +    // Parse definitions
      +    $this->parseDefinitions();
      +
      +    // Init beans
      +    $this->initializeBeans();
      +}
      +

      先看parseAnnotations方法,从代码注释上也可以看出大概,解析注解,接下来我们看下具体是如何实现的。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseAnnotations(): void
      +{
      +    $annotationParser = new AnnotationObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +    $annotationData   = $annotationParser->parseAnnotations($this->annotations, $this->parsers);
      +
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
      +}
      +

      声明了一个AnnotationObjParser对象,调用了parseAnnotations方法。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @param array $annotations
      +    * @param array $parsers
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    */
      +public function parseAnnotations(array $annotations, array $parsers): array
      +{
      +    $this->parsers     = $parsers;
      +    $this->annotations = $annotations;
      +
      +    foreach ($this->annotations as $loadNameSpace => $classes) {
      +        foreach ($classes as $className => $classOneAnnotations) {
      +            $this->parseOneClassAnnotations($className, $classOneAnnotations);
      +        }
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      这里遍历所有的annotation类,循环调用parseOneClassAnnotations进行解析。

      +
      /**
      +    * Parse class all annotations
      +    *
      +    * @param string $className
      +    * @param array  $classOneAnnotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
      +{
      +    // Check class annotation tag
      +    if (!isset($classOneAnnotations['annotation'])) {
      +        throw new AnnotationException(
      +            sprintf('Property or method(%s) with `@xxx` must be define class annotation', $className)
      +        );
      +    }
      +
      +    // Parse class annotations
      +    $classAnnotations = $classOneAnnotations['annotation'];
      +    $reflectionClass  = $classOneAnnotations['reflection'];
      +
      +    $classAry = [
      +        $className,
      +        $reflectionClass,
      +        $classAnnotations
      +    ];
      +
      +    $objectDefinition = $this->parseClassAnnotations($classAry);
      +
      +    // Parse property annotations
      +    $propertyInjects        = [];
      +    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
      +    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
      +        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
      +        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
      +        if ($propertyInject) {
      +            $propertyInjects[$propertyName] = $propertyInject;
      +        }
      +    }
      +
      +    // Parse method annotations
      +    $methodInjects        = [];
      +    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
      +    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
      +        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];
      +
      +        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
      +        if ($methodInject) {
      +            $methodInjects[$methodName] = $methodInject;
      +        }
      +    }
      +
      +    if (!$objectDefinition) {
      +        return;
      +    }
      +
      +    if (!empty($propertyInjects)) {
      +        $objectDefinition->setPropertyInjections($propertyInjects);
      +    }
      +
      +    if (!empty($methodInjects)) {
      +        $objectDefinition->setMethodInjections($methodInjects);
      +    }
      +
      +    // Object definition and class name
      +    $name         = $objectDefinition->getName();
      +    $aliase       = $objectDefinition->getAlias();
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $name;
      +
      +    $this->classNames[$className]   = array_unique($classNames);
      +    $this->objectDefinitions[$name] = $objectDefinition;
      +
      +    if (!empty($aliase)) {
      +        $this->aliases[$aliase] = $name;
      +    }
      +}
      +

      这里可以看到分别有类注解、属性注解和方法注解三类。

      +

      对应官方文档的注解说明

      +
      /**
      +    * @param array $classAry
      +    *
      +    * @return ObjectDefinition|null
      +    */
      +private function parseClassAnnotations(array $classAry): ?ObjectDefinition
      +{
      +    [, , $classAnnotations] = $classAry;
      +
      +    $objectDefinition = null;
      +    foreach ($classAnnotations as $annotation) {
      +        $annotationClass = get_class($annotation);
      +        if (!isset($this->parsers[$annotationClass])) {
      +            continue;
      +        }
      +
      +        $parserClassName  = $this->parsers[$annotationClass];
      +        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
      +
      +        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
      +        if (empty($data)) {
      +            continue;
      +        }
      +
      +        if (count($data) !== 4) {
      +            throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
      +        }
      +
      +        [$name, $className, $scope, $alias] = $data;
      +        $name = empty($name) ? $className : $name;
      +
      +        if (empty($className)) {
      +            throw new InvalidArgumentException(sprintf('%s with class name can not be empty', $annotationClass));
      +        }
      +
      +        // Multiple coverage
      +        $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
      +    }
      +
      +    return $objectDefinition;
      +}
      +

      类注解,这里会调用对应解析类的parse方法。

      +

      这里以websocket-server\src\Annotation\Mapping\WsModule.phpwebsocket-server\src\Annotation\Parser\WsModuleParser.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Mapping;
      +
      +use Doctrine\Common\Annotations\Annotation\Attribute;
      +use Doctrine\Common\Annotations\Annotation\Attributes;
      +use Doctrine\Common\Annotations\Annotation\Required;
      +use Doctrine\Common\Annotations\Annotation\Target;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +
      +/**
      + * Class WebSocket - mark an websocket module handler class
      + *
      + * @since 2.0
      + *
      + * @Annotation
      + * @Target("CLASS")
      + * @Attributes(
      + *     @Attribute("name", type="string"),
      + *     @Attribute("path", type="string"),
      + *     @Attribute("controllers", type="array"),
      + *     @Attribute("messageParser", type="string"),
      + * )
      + */
      +final class WsModule
      +{
      +    /**
      +     * Websocket route path.(it must unique in a application)
      +     *
      +     * @var string
      +     * @Required()
      +     */
      +    private $path = '/';
      +
      +    /**
      +     * Module name.
      +     *
      +     * @var string
      +     */
      +    private $name = '';
      +
      +    /**
      +     * Routing path params binding. eg. {"id"="\d+"}
      +     *
      +     * @var array
      +     */
      +    private $params = [];
      +
      +    /**
      +     * Message controllers of the module
      +     *
      +     * @var string[]
      +     */
      +    private $controllers = [];
      +
      +    /**
      +     * Message parser class for the module
      +     *
      +     * @var string
      +     */
      +    private $messageParser = RawTextParser::class;
      +
      +    /**
      +     * Default message command. Format 'controller.action'
      +     *
      +     * @var string
      +     */
      +    private $defaultCommand = 'home.index';
      +
      +    /**
      +     * Default message opcode for response. please see WEBSOCKET_OPCODE_*
      +     *
      +     * @var int
      +     */
      +    private $defaultOpcode = 0;
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $values
      +     */
      +    public function __construct(array $values)
      +    {
      +        if (isset($values['value'])) {
      +            $this->path = (string)$values['value'];
      +        } elseif (isset($values['path'])) {
      +            $this->path = (string)$values['path'];
      +        }
      +
      +        if (isset($values['name'])) {
      +            $this->name = (string)$values['name'];
      +        }
      +
      +        if (isset($values['params'])) {
      +            $this->params = (array)$values['params'];
      +        }
      +
      +        if (isset($values['controllers'])) {
      +            $this->controllers = (array)$values['controllers'];
      +        }
      +
      +        if (isset($values['messageParser'])) {
      +            $this->messageParser = $values['messageParser'];
      +        }
      +
      +        if (isset($values['defaultOpcode'])) {
      +            $this->defaultOpcode = (int)$values['defaultOpcode'];
      +        }
      +
      +        if (isset($values['defaultCommand'])) {
      +            $this->defaultCommand = $values['defaultCommand'];
      +        }
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getPath(): string
      +    {
      +        return $this->path;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getMessageParser(): string
      +    {
      +        return $this->messageParser;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getDefaultCommand(): string
      +    {
      +        return $this->defaultCommand;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getName(): string
      +    {
      +        return $this->name;
      +    }
      +
      +    /**
      +     * @return string[]
      +     */
      +    public function getControllers(): array
      +    {
      +        return $this->controllers;
      +    }
      +
      +    /**
      +     * @return array
      +     */
      +    public function getParams(): array
      +    {
      +        return $this->params;
      +    }
      +
      +    /**
      +     * @return int
      +     */
      +    public function getDefaultOpcode(): int
      +    {
      +        return $this->defaultOpcode;
      +    }
      +}
      +

      WsModule声明了一个类注解。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Stdlib\Helper\Str;
      +use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +use Swoft\WebSocket\Server\Router\RouteRegister;
      +
      +/**
      + * Class WebSocketParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(WsModule::class)
      + */
      +class WsModuleParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int      $type Class or Method or Property
      +     * @param WsModule $ann  Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $ann): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@WsModule` must be defined on class!');
      +        }
      +
      +        $class = $this->className;
      +
      +        RouteRegister::bindModule($class, [
      +            'path'           => $ann->getPath() ?: Str::getClassName($class, 'Module'),
      +            'name'           => $ann->getName(),
      +            'params'         => $ann->getParams(),
      +            'class'          => $class,
      +            'eventMethods'   => [],
      +            'controllers'    => $ann->getControllers(),
      +            'messageParser'  => $ann->getMessageParser() ?: RawTextParser::class,
      +            'defaultOpcode' => $ann->getDefaultOpcode(),
      +            'defaultCommand' => $ann->getDefaultCommand(),
      +        ]);
      +
      +        return [$class, $class, Bean::SINGLETON, ''];
      +    }
      +}
      +

      按上一篇文章说明,这里WsModuleParser会被标记为注解类WsModule的注解解析类。

      +

      解析注解的时候,会调用WsModuleParserparse方法,这里通过RouteRegister::bindModule做了一些路由操作,这里后续再讲,这里不做深入介绍。

      +

      属性和方法注解,也是类似的,parseAnnotations方法就讲完了。

      +

      回到Container类的init方法,接下来调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    */
      +private function parseDefinitions(): void
      +{
      +    $annotationParser = new DefinitionObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +
      +    // Collect info
      +    $definitionData = $annotationParser->parseDefinitions();
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $definitionData;
      +}
      +

      声明了一个DefinitionObjParser对象,调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    *
      +    * @return array
      +    */
      +public function parseDefinitions(): array
      +{
      +    foreach ($this->definitions as $beanName => $definition) {
      +        if (isset($this->objectDefinitions[$beanName])) {
      +            $objectDefinition = $this->objectDefinitions[$beanName];
      +            $this->resetObjectDefinition($beanName, $objectDefinition, $definition);
      +            continue;
      +        }
      +
      +        $this->createObjectDefinition($beanName, $definition);
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      遍历所有的Bean对象,调用createObjectDefinition方法。

      +
      /**
      +    * Create object definition for definition
      +    *
      +    * @param string $beanName
      +    * @param array  $definition
      +    */
      +private function createObjectDefinition(string $beanName, array $definition): void
      +{
      +    $className = $definition['class'] ?? '';
      +    if (empty($className)) {
      +        throw new InvalidArgumentException(sprintf('%s key for definition must be defined class', $beanName));
      +    }
      +
      +    $objDefinition = new ObjectDefinition($beanName, $className);
      +    $objDefinition = $this->updateObjectDefinitionByDefinition($objDefinition, $definition);
      +
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $beanName;
      +
      +    $this->classNames[$className]       = array_unique($classNames);
      +    $this->objectDefinitions[$beanName] = $objDefinition;
      +}
      +

      声明了ObjectDefinition对象,调用了updateObjectDefinitionByDefinition方法。

      +
      /**
      +    * Update definition
      +    *
      +    * @param ObjectDefinition $objDfn
      +    * @param array            $definition
      +    *
      +    * @return ObjectDefinition
      +    */
      +private function updateObjectDefinitionByDefinition(ObjectDefinition $objDfn, array $definition): ObjectDefinition
      +{
      +    [$constructInject, $propertyInjects, $option] = $this->parseDefinition($definition);
      +
      +    // Set construct inject
      +    if (!empty($constructInject)) {
      +        $objDfn->setConstructorInjection($constructInject);
      +    }
      +
      +    // Set property inject
      +    foreach ($propertyInjects as $propertyName => $propertyInject) {
      +        $objDfn->setPropertyInjection($propertyName, $propertyInject);
      +    }
      +
      +    $scopes = [
      +        Bean::SINGLETON,
      +        Bean::PROTOTYPE,
      +        Bean::REQUEST,
      +    ];
      +
      +    $scope = $option['scope'] ?? '';
      +    $alias = $option['alias'] ?? '';
      +
      +    if (!empty($scope) && !in_array($scope, $scopes, true)) {
      +        throw new InvalidArgumentException('Scope for definition is not undefined');
      +    }
      +
      +    // Update scope
      +    if (!empty($scope)) {
      +        $objDfn->setScope($scope);
      +    }
      +
      +    // Update alias
      +    if (!empty($alias)) {
      +        $objDfn->setAlias($alias);
      +
      +        $objAlias = $objDfn->getAlias();
      +        unset($this->aliases[$objAlias]);
      +
      +        $this->aliases[$alias] = $objDfn->getName();
      +    }
      +
      +    return $objDfn;
      +}
      +

      这里调用了parseDefinition方法进行解析。

      +
      /**
      +    * Parse definition
      +    *
      +    * @param array $definition
      +    *
      +    * @return array
      +    */
      +private function parseDefinition(array $definition): array
      +{
      +    // Remove class key
      +    unset($definition['class']);
      +
      +    // Parse construct
      +    $constructArgs = $definition[0] ?? [];
      +    if (!is_array($constructArgs)) {
      +        throw new InvalidArgumentException('Construct args for definition must be array');
      +    }
      +
      +    // Parse construct args
      +    $argInjects = [];
      +    foreach ($constructArgs as $arg) {
      +        [$argValue, $argIsRef] = $this->getValueByRef($arg);
      +
      +        $argInjects[] = new ArgsInjection($argValue, $argIsRef);
      +    }
      +
      +    // Set construct inject
      +    $constructInject = null;
      +    if (!empty($argInjects)) {
      +        $constructInject = new MethodInjection('__construct', $argInjects);
      +    }
      +
      +    // Remove construct definition
      +    unset($definition[0]);
      +
      +    // Parse definition option
      +    $option = $definition['__option'] ?? [];
      +    if (!is_array($option)) {
      +        throw new InvalidArgumentException('__option for definition must be array');
      +    }
      +
      +    // Remove `__option`
      +    unset($definition['__option']);
      +
      +    // Parse definition properties
      +    $propertyInjects = [];
      +    foreach ($definition as $propertyName => $propertyValue) {
      +        if (!is_string($propertyName)) {
      +            throw new InvalidArgumentException('Property key from definition must be string');
      +        }
      +
      +        [$proValue, $proIsRef] = $this->getValueByRef($propertyValue);
      +
      +        // Parse property for array
      +        if (is_array($proValue)) {
      +            $proValue = $this->parseArrayProperty($proValue);
      +        }
      +
      +        $propertyInject = new PropertyInjection($propertyName, $proValue, $proIsRef);
      +
      +        $propertyInjects[$propertyName] = $propertyInject;
      +    }
      +
      +    return [$constructInject, $propertyInjects, $option];
      +}
      +

      解析__construct方法和传参,解析属性信息。

      +

      回到updateObjectDefinitionByDefinition方法,将__construct和类属性信息注册到ObjectDefinition对象上,到这里parseDefinitions方法执行完毕。

      +

      回到Container类的init方法,接下来调用了initializeBeans方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @throws InvalidArgumentException
      +    * @throws ReflectionException
      +    */
      +private function initializeBeans(): void
      +{
      +    /* @var ObjectDefinition $objectDefinition */
      +    foreach ($this->objectDefinitions as $beanName => $objectDefinition) {
      +        $scope = $objectDefinition->getScope();
      +        // Exclude request
      +        if ($scope === Bean::REQUEST) {
      +            $this->requestDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // Exclude session
      +        if ($scope === Bean::SESSION) {
      +            $this->sessionDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // New bean
      +        $this->newBean($beanName);
      +    }
      +}
      +

      对于scope不为Bean::REQUESTBean::SESSION的,调用newBean方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object
      +    * @throws ReflectionException
      +    */
      +private function newBean(string $beanName, string $id = '')
      +{
      +    // First, check bean whether has been create.
      +    if (isset($this->singletonPool[$beanName]) || isset($this->prototypePool[$beanName])) {
      +        return $this->get($beanName);
      +    }
      +
      +    // Get object definition
      +    $objectDefinition = $this->getNewObjectDefinition($beanName);
      +
      +    $scope     = $objectDefinition->getScope();
      +    $alias     = $objectDefinition->getAlias();
      +    $className = $objectDefinition->getClassName();
      +
      +    // Cache reflection class info
      +    Reflections::cache($className);
      +
      +    // Before initialize bean
      +    $this->beforeInit($beanName, $className, $objectDefinition);
      +
      +    $constructArgs   = [];
      +    $constructInject = $objectDefinition->getConstructorInjection();
      +    if ($constructInject !== null) {
      +        $constructArgs = $this->getConstructParams($constructInject, $id);
      +    }
      +
      +    $propertyInjects = $objectDefinition->getPropertyInjections();
      +
      +    // Proxy class
      +    if ($this->handler) {
      +        $className = $this->handler->classProxy($className);
      +    }
      +
      +    $reflectionClass = new ReflectionClass($className);
      +    $reflectObject   = $this->newInstance($reflectionClass, $constructArgs);
      +
      +    // Inject properties values
      +    $this->newProperty($reflectObject, $reflectionClass, $propertyInjects, $id);
      +
      +    // Alias
      +    if (!empty($alias)) {
      +        $this->aliases[$alias] = $beanName;
      +    }
      +
      +    // Call init method if exist
      +    if ($reflectionClass->hasMethod(self::INIT_METHOD)) {
      +        $reflectObject->{self::INIT_METHOD}();
      +    }
      +
      +    return $this->setNewBean($beanName, $scope, $reflectObject, $id);
      +}
      +

      通过反射实例化Bean对应的类,注册对应的属性。

      +

      如果类存在self::INIT_METHOD方法,执行此方法。

      +
      /**
      +    * @param string $beanName
      +    * @param string $scope
      +    * @param object $object
      +    * @param string $id
      +    *
      +    * @return object
      +    */
      +private function setNewBean(string $beanName, string $scope, $object, string $id = '')
      +{
      +    switch ($scope) {
      +        case Bean::SINGLETON: // Singleton
      +            $this->singletonPool[$beanName] = $object;
      +            break;
      +        case Bean::PROTOTYPE:
      +            $this->prototypePool[$beanName] = $object;
      +            // Clone it
      +            $object = clone $object;
      +            break;
      +        case Bean::REQUEST:
      +            $this->requestPool[$id][$beanName] = $object;
      +            break;
      +        case Bean::SESSION:
      +            $this->sessionPool[$id][$beanName] = $object;
      +            break;
      +    }
      +
      +    return $object;
      +}
      +

      setNewBean方法,根据对应的scope信息,将实例化后的反射类注册到对应的类属性上。

      +

      到这里BeanProcessor类就执行完了。

      +]]>
      +
      + + Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + Thu, 29 Aug 2019 19:11:04 +0800 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + <p>上一篇介绍了,<code>SwoftApplication</code>里定义了6个Processor对象。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protected function processors(): array +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> return [ +</span></span><span style="display:flex;"><span> new EnvProcessor($this), +</span></span><span style="display:flex;"><span> new ConfigProcessor($this), +</span></span><span style="display:flex;"><span> new AnnotationProcessor($this), +</span></span><span style="display:flex;"><span> new BeanProcessor($this), +</span></span><span style="display:flex;"><span> new EventProcessor($this), +</span></span><span style="display:flex;"><span> new ConsoleProcessor($this), +</span></span><span style="display:flex;"><span> ]; +</span></span><span style="display:flex;"><span> } +</span></span></code></pre></div><p>所有的Processor实现都在<code>framework\src\Processor</code>目录下。</p> + 上一篇介绍了,SwoftApplication里定义了6个Processor对象。

      +
      protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      所有的Processor实现都在framework\src\Processor目录下。

      +
        +
      1. +

        EnvProcessor,运行环境检查。

        +
      2. +
      3. +

        ConfigProcessor,配置相关。

        +
      4. +
      5. +

        AnnotationProcessor,注解相关。

        +
      6. +
      7. +

        BeanProcessor,Bean相关。

        +
      8. +
      9. +

        EventProcessor,事件相关。

        +
      10. +
      11. +

        ConsoleProcessor,命令行输入相关。

        +
      12. +
      +

      今天先讲一下AnnotationProcessor这个模块的实现。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Processor;
      +
      +use Exception;
      +use Swoft\Annotation\AnnotationRegister;
      +use Swoft\Log\Helper\CLog;
      +
      +/**
      + * Annotation processor
      + * @since 2.0
      + */
      +class AnnotationProcessor extends Processor
      +{
      +    /**
      +     * Handle annotation
      +     *
      +     * @return bool
      +     * @throws Exception
      +     */
      +    public function handle(): bool
      +    {
      +        if (!$this->application->beforeAnnotation()) {
      +            CLog::warning('Stop annotation processor by beforeAnnotation return false');
      +            return false;
      +        }
      +
      +        $app = $this->application;
      +
      +        // Find AutoLoader classes. Parse and collect annotations.
      +        AnnotationRegister::load([
      +            'inPhar'               => \IN_PHAR,
      +            'basePath'             => $app->getBasePath(),
      +            'notifyHandler'        => [$this, 'notifyHandler'],
      +            'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
      +            'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
      +        ]);
      +
      +        $stats = AnnotationRegister::getClassStats();
      +
      +        CLog::info(
      +            'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
      +            $stats['autoloader'],
      +            $stats['annotation'],
      +            $stats['parser']
      +        );
      +
      +        return $this->application->afterAnnotation();
      +    }
      +
      +    /**
      +     * @param string $type
      +     * @param string $target
      +     * @see \Swoft\Annotation\Resource\AnnotationResource::load()
      +     */
      +    public function notifyHandler(string $type, $target): void
      +    {
      +        switch ($type) {
      +            case 'excludeNs':
      +                CLog::debug('Exclude namespace %s', $target);
      +                break;
      +            case 'noLoaderFile':
      +                CLog::debug('No autoloader on %s', $target);
      +                break;
      +            case 'noLoaderClass':
      +                CLog::debug('Autoloader class not exist %s', $target);
      +                break;
      +            case 'findLoaderClass':
      +                CLog::debug('Find autoloader %s', $target);
      +                break;
      +            case 'addLoaderClass':
      +                CLog::debug('Parse autoloader %s', $target);
      +                break;
      +            case 'noExistClass':
      +                CLog::debug('Skip interface or trait %s', $target);
      +                break;
      +        }
      +    }
      +}
      +

      核心逻辑调用AnnotationRegister类的load方法,定义如下。

      +
      /**
      +    * Load annotation class
      +    *
      +    * @param array $config
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function load(array $config = []): void
      +{
      +    $resource = new AnnotationResource($config);
      +    $resource->load();
      +}
      +

      这里又调用了AnnotationResource类的load方法,定义如下。

      +
      /**
      +    * Load annotation resource by find ClassLoader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function load(): void
      +{
      +    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
      +
      +    foreach ($prefixDirsPsr4 as $ns => $paths) {
      +        // Only scan namespaces
      +        if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // It is excluded psr4 prefix
      +        if ($this->isExcludedPsr4Prefix($ns)) {
      +            AnnotationRegister::registerExcludeNs($ns);
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // Find package/component loader class
      +        foreach ($paths as $path) {
      +            $loaderFile = $this->getAnnotationClassLoaderFile($path);
      +            if (!file_exists($loaderFile)) {
      +                $this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
      +                continue;
      +            }
      +
      +            $loaderClass = $this->getAnnotationLoaderClassName($ns);
      +            if (!class_exists($loaderClass)) {
      +                $this->notify('noLoaderClass', $loaderClass);
      +                continue;
      +            }
      +
      +            $loaderObject = new $loaderClass();
      +            if (!$loaderObject instanceof LoaderInterface) {
      +                $this->notify('invalidLoader', $loaderFile);
      +                continue;
      +            }
      +
      +            $this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
      +
      +            // If is disable, will skip scan annotation classes
      +            if (!isset($this->disabledAutoLoaders[$loaderClass])) {
      +                AnnotationRegister::registerAutoLoaderFile($loaderFile);
      +                $this->notify('addLoaderClass', $loaderClass);
      +                $this->loadAnnotation($loaderObject);
      +            }
      +
      +            // Storage auto loader to register
      +            AnnotationRegister::addAutoLoader($ns, $loaderObject);
      +        }
      +    }
      +}
      +

      通过getPrefixesPsr4方法获取所有自动加载的命名空间和目录,遍历目录下的AutoLoader.php文件。

      +

      通过registerAutoLoaderFile注册自动加载文件到AnnotationRegister对象上。

      +

      然后调用了loadAnnotation方法,传入的是一个autoload对象。

      +
      /**
      +    * Load annotations from an component loader config.
      +    *
      +    * @param LoaderInterface $loader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function loadAnnotation(LoaderInterface $loader): void
      +{
      +    $nsPaths = $loader->getPrefixDirs();
      +
      +    foreach ($nsPaths as $ns => $path) {
      +        $iterator = DirectoryHelper::recursiveIterator($path);
      +
      +        /* @var SplFileInfo $splFileInfo */
      +        foreach ($iterator as $splFileInfo) {
      +            $filePath = $splFileInfo->getPathname();
      +            // $splFileInfo->isDir();
      +            if (is_dir($filePath)) {
      +                continue;
      +            }
      +
      +            $fileName  = $splFileInfo->getFilename();
      +            $extension = $splFileInfo->getExtension();
      +
      +            if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
      +                continue;
      +            }
      +
      +            // It is exclude filename
      +            if (isset($this->excludedFilenames[$fileName])) {
      +                AnnotationRegister::registerExcludeFilename($fileName);
      +                continue;
      +            }
      +
      +            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
      +            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
      +            $className = sprintf('%s%s', $ns, $pathName);
      +
      +            // Fix repeat included file bug
      +            $autoload = in_array($filePath, $this->includedFiles, true);
      +
      +            // Will filtering: interfaces and traits
      +            if (!class_exists($className, !$autoload)) {
      +                $this->notify('noExistClass', $className);
      +                continue;
      +            }
      +
      +            // Parse annotation
      +            $this->parseAnnotation($ns, $className);
      +        }
      +    }
      +}
      +

      通过getPrefixDirs获取当前命名空间的目录,然后通过recursiveIterator遍历目录下的文件。

      +

      排除目录和非.php结尾的文件,最后会调用parseAnnotation方法。

      +
      /**
      +    * Parser annotation
      +    *
      +    * @param string $namespace
      +    * @param string $className
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseAnnotation(string $namespace, string $className): void
      +{
      +    // Annotation reader
      +    $reflectionClass = new ReflectionClass($className);
      +
      +    // Fix ignore abstract
      +    if ($reflectionClass->isAbstract()) {
      +        return;
      +    }
      +    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
      +
      +    if (!empty($oneClassAnnotation)) {
      +        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
      +    }
      +}
      +

      这里调用了parseOneClassAnnotation方法。

      +
      /**
      +    * Parse an class annotation
      +    *
      +    * @param ReflectionClass $reflectionClass
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
      +{
      +    // Annotation reader
      +    $reader    = new AnnotationReader();
      +    $className = $reflectionClass->getName();
      +
      +    $oneClassAnnotation = [];
      +    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);
      +
      +    // Register annotation parser
      +    foreach ($classAnnotations as $classAnnotation) {
      +        if ($classAnnotation instanceof AnnotationParser) {
      +            $this->registerParser($className, $classAnnotation);
      +
      +            return [];
      +        }
      +    }
      +
      +    // Class annotation
      +    if (!empty($classAnnotations)) {
      +        $oneClassAnnotation['annotation'] = $classAnnotations;
      +        $oneClassAnnotation['reflection'] = $reflectionClass;
      +    }
      +
      +    // Property annotation
      +    $reflectionProperties = $reflectionClass->getProperties();
      +    foreach ($reflectionProperties as $reflectionProperty) {
      +        $propertyName        = $reflectionProperty->getName();
      +        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);
      +
      +        if (!empty($propertyAnnotations)) {
      +            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
      +            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
      +        }
      +    }
      +
      +    // Method annotation
      +    $reflectionMethods = $reflectionClass->getMethods();
      +    foreach ($reflectionMethods as $reflectionMethod) {
      +        $methodName        = $reflectionMethod->getName();
      +        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);
      +
      +        if (!empty($methodAnnotations)) {
      +            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
      +            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
      +        }
      +    }
      +
      +    $parentReflectionClass = $reflectionClass->getParentClass();
      +    if ($parentReflectionClass !== false) {
      +        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
      +        if (!empty($parentClassAnnotation)) {
      +            $oneClassAnnotation['parent'] = $parentClassAnnotation;
      +        }
      +    }
      +
      +    return $oneClassAnnotation;
      +}
      +

      这里就是解析注解了,可以看到分别有类注解、属性注解和方法注解三类。

      +

      这里注意这一段代码。

      +
      // Register annotation parser
      +foreach ($classAnnotations as $classAnnotation) {
      +    if ($classAnnotation instanceof AnnotationParser) {
      +        $this->registerParser($className, $classAnnotation);
      +
      +        return [];
      +    }
      +}
      +

      遍历注解类,如果注解属于AnnotationParser实例,这里调用registerParser进行注册。

      +
      /**
      +    * @param string $annotationClass
      +    * @param string $parserClassName
      +    */
      +public static function registerParser(string $annotationClass, string $parserClassName): void
      +{
      +    self::$classStats['parser']++;
      +    self::$parsers[$annotationClass] = $parserClassName;
      +}
      +

      回到上一个方法,解析完后,又调用了AnnotationRegister类的registerAnnotation方法进行注册。

      +
      /**
      +    * @param string $loadNamespace
      +    * @param string $className
      +    * @param array  $classAnnotation
      +    */
      +public static function registerAnnotation(string $loadNamespace, string $className, array $classAnnotation): void
      +{
      +    self::$classStats['annotation']++;
      +    self::$annotations[$loadNamespace][$className] = $classAnnotation;
      +}
      +

      至此,整个AnnotationProcessor加载完毕,这里AnnotationRegister类里会有annotationsparsers两个属性,这个信息在后面的BeanProcessor里还会用到。

      +]]>
      +
      + + Swoft 框架运行分析(一) + https://liudon.com/posts/swoft-execution-analysis/ + Thu, 29 Aug 2019 17:22:28 +0800 + https://liudon.com/posts/swoft-execution-analysis/ + <blockquote> +<p>Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。</p> +</blockquote> +<p>以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。</p> +<p>刚开始看的时候,感觉自己像个原始人,完全看不懂。</p> + +

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。

      + +

      以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。

      +

      刚开始看的时候,感觉自己像个原始人,完全看不懂。

      +

      官方文档没有介绍Swoft的实现,网上的一些文章跟当前版本代码已经不一致了。

      +

      自己花了一周时间,终于梳理清楚了,看完更觉得自己是个原始人了。

      +

      使用的框架组件版本为:

      +
      swoft-2.0.5
      +swoft-component-2.0.5
      +

      这里以Swoft启动http server为例。

      +
      php bin/swoft http:start
      +

      执行上述命令,启动http server。

      +

      这里执行的是bin/swoft文件。

      +
      #!/usr/bin/env php
      +<?php declare(strict_types=1);
      +
      +// Bootstrap
      +require_once __DIR__ . '/bootstrap.php';
      +
      +Swoole\Coroutine::set([
      +    'max_coroutine' => 300000,
      +]);
      +
      +// Run application
      +(new \App\Application())->run();
      +

      这里引入bootstrap.php文件,引入composer自动加载文件。

      +
      <?php
      +// Composer autoload
      +require_once dirname(__DIR__) . '/vendor/autoload.php';
      +

      然后执行Swoft\App\Application类下的run方法。

      +
      <?php declare(strict_types=1);
      +
      +namespace App;
      +
      +use Swoft\SwoftApplication;
      +use function date_default_timezone_set;
      +
      +/**
      + * Class Application
      + *
      + * @since 2.0
      + */
      +class Application extends SwoftApplication
      +{
      +    protected function beforeInit(): void
      +    {
      +        parent::beforeInit();
      +
      +        date_default_timezone_set('Asia/Shanghai');
      +    }
      +}
      +

      这里继承了Swoft\SwoftApplication类,这里只粘贴了部分代码。

      +
      /**
      + * Swoft application
      + *
      + * @since 2.0
      + */
      +class SwoftApplication implements SwoftInterface, ApplicationInterface
      +{
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $config
      +     */
      +    public function __construct(array $config = [])
      +    {
      +        // Check runtime env
      +        SwoftHelper::checkRuntime();
      +
      +        // Storage as global static property.
      +        Swoft::$app = $this;
      +
      +        // Before init
      +        $this->beforeInit();
      +
      +        // Init console logger
      +        $this->initCLogger();
      +
      +        // Can setting properties by array
      +        if ($config) {
      +            ObjectHelper::init($this, $config);
      +        }
      +
      +        // Init application
      +        $this->init();
      +
      +        CLog::info('Project path is <info>%s</info>', $this->basePath);
      +
      +        // After init
      +        $this->afterInit();
      +    }
      +
      +    protected function init(): void
      +    {
      +        // Init system path aliases
      +        $this->findBasePath();
      +        $this->setSystemAlias();
      +
      +        $processors = $this->processors();
      +
      +        $this->processor = new ApplicationProcessor($this);
      +        $this->processor->addFirstProcessor(...$processors);
      +    }
      +
      +    /**
      +     * Run application
      +     */
      +    public function run(): void
      +    {
      +        if (!$this->beforeRun()) {
      +            return;
      +        }
      +
      +        $this->processor->handle();
      +    }
      +
      +    /**
      +     * @return ProcessorInterface[]
      +     */
      +    protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      __construct方法里检查运行环境,初始化日志组件,然后调用了init方法。

      +

      init方法里声明了processor对象。

      +

      processors方法定义了Swoft框架的6个Processor对象。

      +

      run方法里直接调用processor对象的handler方法。

      +
      <?php
      +
      +namespace Swoft\Processor;
      +
      +use Swoft\Stdlib\Helper\ArrayHelper;
      +use function get_class;
      +
      +/**
      + * Application processor
      + * @since 2.0
      + */
      +class ApplicationProcessor extends Processor
      +{
      +    /**
      +     * @var ProcessorInterface[]
      +     */
      +    private $processors = [];
      +
      +    /**
      +     * Handle application processors
      +     */
      +    public function handle(): bool
      +    {
      +        $disabled = $this->application->getDisabledProcessors();
      +
      +        foreach ($this->processors as $processor) {
      +            $class = get_class($processor);
      +
      +            // If is disabled, skip handle.
      +            if (isset($disabled[$class])) {
      +                continue;
      +            }
      +
      +            $processor->handle();
      +        }
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add first processor
      +     *
      +     * @param Processor[] $processor
      +     * @return bool
      +     */
      +    public function addFirstProcessor(Processor ...$processor): bool
      +    {
      +        array_unshift($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add last processor
      +     *
      +     * @param Processor[] $processor
      +     *
      +     * @return bool
      +     */
      +    public function addLastProcessor(Processor ...$processor): bool
      +    {
      +        array_push($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add processors
      +     *
      +     * @param int         $index
      +     * @param Processor[] $processors
      +     *
      +     * @return bool
      +     */
      +    public function addProcessor(int $index, Processor  ...$processors): bool
      +    {
      +        ArrayHelper::insert($this->processors, $index, ...$processors);
      +
      +        return true;
      +    }
      +}
      +

      addFirstProcessor方法把process对象赋值给$this->processors

      +

      handle方法遍历processors对象,循环执行handle方法。

      +

      Swoft的核心逻辑都是靠上面定义的6个Processor模块实现的,接下来一个一个分析。

      +]]>
      +
      + + BCMath 与 科学计数 + https://liudon.com/posts/bcmath-and-exponential-notation/ + Fri, 16 Aug 2019 19:34:34 +0800 + https://liudon.com/posts/bcmath-and-exponential-notation/ + <p>代码如下</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>&lt;?php +</span></span><span style="display:flex;"><span>echo 9.99997600 + 2.4E-5; +</span></span><span style="display:flex;"><span>echo &#34;\n===\n&#34;; +</span></span><span style="display:flex;"><span>echo bcadd(9.99997600, 2.4E-5, 8); +</span></span></code></pre></div><p>结果为</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>10 +</span></span><span style="display:flex;"><span>=== +</span></span><span style="display:flex;"><span>9.99997600 +</span></span></code></pre></div><p>问了朋友,查了各种资料,终于在PHP手册里发现了这段话。</p> +<blockquote> +<p>Caution +Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).</p> + 代码如下

      +
      <?php
      +echo 9.99997600 + 2.4E-5;
      +echo "\n===\n";
      +echo bcadd(9.99997600, 2.4E-5, 8);
      +

      结果为

      +
      10
      +===
      +9.99997600
      +

      问了朋友,查了各种资料,终于在PHP手册里发现了这段话。

      +
      +

      Caution +Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point).

      +
      +

      PHP的BCMath方法不支持科学计数

      +

      解决方法:

      +
      echo bcadd(9.99997600, number_format(2.4E-5, 8, '.', ''), 8);
      +

      PHP里浮点数相关的运算一定要使用BCMath函数!

      +]]>
      +
      + + Flink Could Not Resolve Resourcemanager Address + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + Thu, 28 Mar 2019 13:00:50 +0800 + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + <p>什么是Flink。</p> +<blockquote> +<p>Apache Flink® - Stateful Computations over Data Streams</p> +</blockquote> +<p>Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。</p> + 什么是Flink。

      +
      +

      Apache Flink® - Stateful Computations over Data Streams

      +
      +

      Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。

      +

      这里使用单机模式

      +

      问题表现

      +

      启动Flink

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ./bin/start-cluster.sh 
      +Starting cluster.
      +Starting standalonesession daemon on host VM_80_180_centos.
      +Starting taskexecutor daemon on host VM_80_180_centos.
      +

      查看进程

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# jps
      +10442 StandaloneSessionClusterEntrypoint
      +11067 Jps
      +10909 TaskManagerRunner
      +[root@VM_80_180_centos /usr/local/flink-1.7.2]# 
      +

      查看日志,发现"Could not resolve ResourceManager address"的错误。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# tail -f log/flink-root-taskexecutor-*.log
      +
      +2019-03-27 19:43:23,804 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +2019-03-27 19:43:43,843 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +

      访问Flink的web页面,发现task数全为0.

      +

      + +flink no task + + +

      +

      问题原因:

      +

      + +taskmanager.host + + +

      +

      Flink的taskmanager.host默认为空,会使用hostname。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ping VM_80_180_centos
      +PING VM_80_180_centos (100.125.80.180) 56(84) bytes of data.
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=1 ttl=64 time=0.022 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=2 ttl=64 time=0.038 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=3 ttl=64 time=0.038 ms
      +

      Flink的jobmanager.host默认为localhost。

      +

      这里jobmanager和taskmanager绑定的ip不一样,导致出错。

      +

      解决办法:

      +
      vim conf/flink-conf.yaml
      +
      +添加下面这行配置
      +taskmanager.host: localhost
      +

      保存退出,然后重新启动Flink,这个时候在web端就可以看到有可用task了。

      +

      + +flink web + + +

      +]]>
      +
      + + 解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错 + https://liudon.com/posts/there-are-no-packages-available-for-installation/ + Fri, 11 Jan 2019 17:13:14 +0800 + https://liudon.com/posts/there-are-no-packages-available-for-installation/ + <p>今天安装hugofy的包时,一直遇到&quot;There Are No Packages Available for Installation&quot;的错误。 +按网上的教程,配置host,配置代理都不起作用。</p> + 今天安装hugofy的包时,一直遇到"There Are No Packages Available for Installation"的错误。 +按网上的教程,配置host,配置代理都不起作用。

      +

      本机确定是可以访问https://packagecontrol.io/channel_v3.json这个地址的。

      +

      然后按教程把这文件放到本地,配置channel指向本地这个文件,然后提示json解析失败。 +然后检查这个文件,发现文件好像不全。然后换到其他机器curl这个地址,发现下载下来的文件确实不全,不是合法的json内容。

      +

      + +下载文件内容截图 + + +

      +

      又搜索一番后,找到一个case。

      +

      Package Control: There are no packages available for installation/Server Error

      +

      原来是官方的文件下载出问题了,可以先按上面链接里的方法修改,验证可行。

      +
      Meanwhile, you can add
      +"channels": [ "https://erhan.in/channel_v3.json" ],
      +to Preferences > Package Settings > Package Control > Settings - User file.
      +
      +This is the latest snapshot of the original JSON file from web.archive.org.
      +
      ]]>
      +
      + + 2019,新开始 + https://liudon.com/posts/the-first-post/ + Wed, 09 Jan 2019 15:17:04 +0800 + https://liudon.com/posts/the-first-post/ + <p>从2011年开始写博客,博客程序从WordPress换成Typecho。 +早就有想法换成静态博客,一直没时间搞。</p> +<p>2019年了,新年新气象,用hugo + github pages搞了个新博客。</p> + 从2011年开始写博客,博客程序从WordPress换成Typecho。 +早就有想法换成静态博客,一直没时间搞。

      +

      2019年了,新年新气象,用hugo + github pages搞了个新博客。

      +

      具体部署过程参考文章: +利用Travis CI和Hugo將Blog自動部署到Github Pages

      +

      这篇文章就是通过这种方式来更新的,感觉很是神奇。 +再也不用关注服务器性能这些东西了,只需要专心写字就好了。

      +

      接下来只需要搞定自定义域名了,域名还在认证中,无法做解析。

      +

      自定义域名也搞定了,以后就可以正式切到新博客了。

      +

      老博客只做备份了,不再更新了。

      +]]>
      +
      +
      +
      diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210.png new file mode 100644 index 000000000..da0ce5d6b Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu14733520709407985521.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu14733520709407985521.png new file mode 100644 index 000000000..c882eb974 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu14733520709407985521.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu321491354137511694.webp b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu321491354137511694.webp new file mode 100644 index 000000000..983e6b2e0 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214210_hu321491354137511694.webp differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908.png new file mode 100644 index 000000000..528527222 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu16390248430034468739.webp b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu16390248430034468739.webp new file mode 100644 index 000000000..3b02d7771 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu16390248430034468739.webp differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu2006163032842884511.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu2006163032842884511.png new file mode 100644 index 000000000..84e741acf Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-214908_hu2006163032842884511.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553.png new file mode 100644 index 000000000..f1536c8f8 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu15131831393258794809.png b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu15131831393258794809.png new file mode 100644 index 000000000..7a5f22cbd Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu15131831393258794809.png differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu3229473731105826581.webp b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu3229473731105826581.webp new file mode 100644 index 000000000..647ec3510 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/20240904-215553_hu3229473731105826581.webp differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16.jpg b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16.jpg new file mode 100644 index 000000000..eb7f1dde5 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16.jpg differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu15084167724204188645.jpg b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu15084167724204188645.jpg new file mode 100644 index 000000000..3202ca45f Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu15084167724204188645.jpg differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu3230142415535382766.webp b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu3230142415535382766.webp new file mode 100644 index 000000000..ec46ce0e4 Binary files /dev/null and b/posts/keep-your-ipfs-site-online-with-filebase-ipns/WechatIMG16_hu3230142415535382766.webp differ diff --git a/posts/keep-your-ipfs-site-online-with-filebase-ipns/index.html b/posts/keep-your-ipfs-site-online-with-filebase-ipns/index.html new file mode 100644 index 000000000..0daaa0170 --- /dev/null +++ b/posts/keep-your-ipfs-site-online-with-filebase-ipns/index.html @@ -0,0 +1,67 @@ +让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 | 流动 +

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      背景

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      Github文档,官方已经不再更新了。

      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      折腾记录

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      生成密钥

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      所以需要将云主机的密钥导出后,导入到filebase

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      chat

      查看已有密钥:

      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      filebase导入key要求为base64编码,将其转为base64编码:

      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      创建NAME

      进入filebase控制台,点击Create Name

      input

      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      修改workflow
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      提交后,执行workflow,在执行结果里找到IPNS地址。

      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +

      更新域名的dnslink值:

      普通域名

      dns

      eth域名

      eth

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      💬评论
      \ No newline at end of file diff --git a/posts/mid-autumn-festival-climb/WechatIMG19.jpg b/posts/mid-autumn-festival-climb/WechatIMG19.jpg new file mode 100644 index 000000000..37b5edfbc Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG19.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG19_hu1966869798560919248.jpg b/posts/mid-autumn-festival-climb/WechatIMG19_hu1966869798560919248.jpg new file mode 100644 index 000000000..5fb88aa83 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG19_hu1966869798560919248.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG19_hu558395342516107522.webp b/posts/mid-autumn-festival-climb/WechatIMG19_hu558395342516107522.webp new file mode 100644 index 000000000..468667614 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG19_hu558395342516107522.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG20.jpg b/posts/mid-autumn-festival-climb/WechatIMG20.jpg new file mode 100644 index 000000000..ccd5fc569 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG20.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG20_hu11028198348033128639.webp b/posts/mid-autumn-festival-climb/WechatIMG20_hu11028198348033128639.webp new file mode 100644 index 000000000..b74cd97ca Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG20_hu11028198348033128639.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG20_hu8922872351200138014.jpg b/posts/mid-autumn-festival-climb/WechatIMG20_hu8922872351200138014.jpg new file mode 100644 index 000000000..4a45e6353 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG20_hu8922872351200138014.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG21.jpg b/posts/mid-autumn-festival-climb/WechatIMG21.jpg new file mode 100644 index 000000000..028a9be0f Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG21.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG21_hu13792731692067208963.jpg b/posts/mid-autumn-festival-climb/WechatIMG21_hu13792731692067208963.jpg new file mode 100644 index 000000000..a807667b9 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG21_hu13792731692067208963.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG21_hu4167798503274414761.webp b/posts/mid-autumn-festival-climb/WechatIMG21_hu4167798503274414761.webp new file mode 100644 index 000000000..2bfafd48d Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG21_hu4167798503274414761.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG22.jpg b/posts/mid-autumn-festival-climb/WechatIMG22.jpg new file mode 100644 index 000000000..97d131cc4 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG22.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG22_hu12703790222321786933.jpg b/posts/mid-autumn-festival-climb/WechatIMG22_hu12703790222321786933.jpg new file mode 100644 index 000000000..764aa2436 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG22_hu12703790222321786933.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG22_hu9904456584325224195.webp b/posts/mid-autumn-festival-climb/WechatIMG22_hu9904456584325224195.webp new file mode 100644 index 000000000..c9ef26777 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG22_hu9904456584325224195.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG23.jpg b/posts/mid-autumn-festival-climb/WechatIMG23.jpg new file mode 100644 index 000000000..ddfed368a Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG23.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG23_hu10384701062457344398.jpg b/posts/mid-autumn-festival-climb/WechatIMG23_hu10384701062457344398.jpg new file mode 100644 index 000000000..17ac0dbf5 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG23_hu10384701062457344398.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG23_hu8439687957801705819.webp b/posts/mid-autumn-festival-climb/WechatIMG23_hu8439687957801705819.webp new file mode 100644 index 000000000..f031cae36 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG23_hu8439687957801705819.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG24.jpg b/posts/mid-autumn-festival-climb/WechatIMG24.jpg new file mode 100644 index 000000000..5b7c18637 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG24.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG24_hu12278615535880005190.jpg b/posts/mid-autumn-festival-climb/WechatIMG24_hu12278615535880005190.jpg new file mode 100644 index 000000000..ad61652c5 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG24_hu12278615535880005190.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG24_hu15078672566446588769.webp b/posts/mid-autumn-festival-climb/WechatIMG24_hu15078672566446588769.webp new file mode 100644 index 000000000..163ac5517 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG24_hu15078672566446588769.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG25.jpg b/posts/mid-autumn-festival-climb/WechatIMG25.jpg new file mode 100644 index 000000000..b4c6b1285 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG25.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG25_hu11001828482871175540.webp b/posts/mid-autumn-festival-climb/WechatIMG25_hu11001828482871175540.webp new file mode 100644 index 000000000..7d2b4937c Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG25_hu11001828482871175540.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG25_hu5030880584826212347.jpg b/posts/mid-autumn-festival-climb/WechatIMG25_hu5030880584826212347.jpg new file mode 100644 index 000000000..84801dd67 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG25_hu5030880584826212347.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG26.jpg b/posts/mid-autumn-festival-climb/WechatIMG26.jpg new file mode 100644 index 000000000..81ca5cf0f Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG26.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG26_hu15654812133648577655.jpg b/posts/mid-autumn-festival-climb/WechatIMG26_hu15654812133648577655.jpg new file mode 100644 index 000000000..20f0f9b84 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG26_hu15654812133648577655.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG26_hu5173877404146564428.webp b/posts/mid-autumn-festival-climb/WechatIMG26_hu5173877404146564428.webp new file mode 100644 index 000000000..7dabddb62 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG26_hu5173877404146564428.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG27.jpg b/posts/mid-autumn-festival-climb/WechatIMG27.jpg new file mode 100644 index 000000000..9a4378fb3 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG27.jpg differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG27_hu13786572578730596640.webp b/posts/mid-autumn-festival-climb/WechatIMG27_hu13786572578730596640.webp new file mode 100644 index 000000000..86a3b2e74 Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG27_hu13786572578730596640.webp differ diff --git a/posts/mid-autumn-festival-climb/WechatIMG27_hu17834791688016845712.jpg b/posts/mid-autumn-festival-climb/WechatIMG27_hu17834791688016845712.jpg new file mode 100644 index 000000000..57e846b8a Binary files /dev/null and b/posts/mid-autumn-festival-climb/WechatIMG27_hu17834791688016845712.jpg differ diff --git a/posts/mid-autumn-festival-climb/index.html b/posts/mid-autumn-festival-climb/index.html new file mode 100644 index 000000000..8aa77dcf3 --- /dev/null +++ b/posts/mid-autumn-festival-climb/index.html @@ -0,0 +1,9 @@ +中秋爬山 | 流动 +

      中秋爬山

      中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。

      晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。

      园博园开了灯会,微博上看说是人巨多,还是放弃吧。

      跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。

      第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。

      还好不远,山也不高,我们也不着急,就当遛弯。

      来了好几次了,进园就直奔主题:爬。

      这次我俩先走了一段山路,虽然是台阶,但是确实快。

      昨天玩的太累,我们商量着还是继续走坡道吧。

      花了40分钟左右登顶,最快的一次记录了。

      登顶

      爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。

      小憩

      今天天气一般,能见度不高,远处都是灰蒙蒙的。

      歇到1点多,我俩开始下山。

      之前在微博看到陈晓卿分享的一家新疆馆子白钻美食,决定晚上带娃去尝尝。

      坐了1个半小时的地铁,到了吕营大街。

      高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。

      我俩傻乎乎的爬楼上来,累个够呛。

      建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。

      4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。

      馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂

      不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。

      羊腿抓饭

      羊肉串

      然后又点了一份过油拌面,面条非常劲道,味道非常棒。

      过油拌面

      喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。

      砖茶

      店不大,但是味道挺好,推荐去试试,就是有点远。

      白钻美食

      吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。

      今天是暴走的一天。

      健身记录

      💬评论
      \ No newline at end of file diff --git a/posts/my-google-adsense-approval-journey/20240916-233121.png b/posts/my-google-adsense-approval-journey/20240916-233121.png new file mode 100644 index 000000000..bda6a69af Binary files /dev/null and b/posts/my-google-adsense-approval-journey/20240916-233121.png differ diff --git a/posts/my-google-adsense-approval-journey/20240916-233753.png b/posts/my-google-adsense-approval-journey/20240916-233753.png new file mode 100644 index 000000000..dfb1f97ea Binary files /dev/null and b/posts/my-google-adsense-approval-journey/20240916-233753.png differ diff --git a/posts/my-google-adsense-approval-journey/20240916-233753_hu15305536531567989767.webp b/posts/my-google-adsense-approval-journey/20240916-233753_hu15305536531567989767.webp new file mode 100644 index 000000000..b0c1395ed Binary files /dev/null and b/posts/my-google-adsense-approval-journey/20240916-233753_hu15305536531567989767.webp differ diff --git a/posts/my-google-adsense-approval-journey/20240916-233753_hu9972045121987141792.png b/posts/my-google-adsense-approval-journey/20240916-233753_hu9972045121987141792.png new file mode 100644 index 000000000..e5083371d Binary files /dev/null and b/posts/my-google-adsense-approval-journey/20240916-233753_hu9972045121987141792.png differ diff --git a/posts/my-google-adsense-approval-journey/index.html b/posts/my-google-adsense-approval-journey/index.html new file mode 100644 index 000000000..9b359f60a --- /dev/null +++ b/posts/my-google-adsense-approval-journey/index.html @@ -0,0 +1,9 @@ +Google Adsense的审核之旅 | 流动 +

      Google Adsense的审核之旅

      中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。

      偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。

      看着新博客每天也有了一些访问,打算申请Google Adsense,补充些维护成本。

      按之前的流程搞了一遍,提交了申请。

      结果过了1周多,收到审核不通过邮件,说是不符合规范:低质内容,质量不高。

      搜了一下,说是现在新网站审核门槛高了。

      不放弃,继续申请呗。

      approval google adsense

      从3月份开始,申请了7次,全部被拒。

      尤其是8月25日被拒后,提示我审核次数过多,必须得等到8月31日以后才能再次申请。

      上社区发了帖子,咨询到底是什么原因,结果也没收到答复。

      这个时候,就已经有点心灰意冷,想要放弃了。

      9月5日的时候,想着再最后申请一把试试看,再不通过就算了。

      等了1周多,感觉这次估计又悬了,已经放弃了,结果今天竟然审核通过了。

      历经了8次申请,耗时半年,终于申请下来了,算是这段时间难得的一件好事。

      💬评论
      \ No newline at end of file diff --git a/posts/my-journey-of-learning-to-drive/202303242116962.jpeg b/posts/my-journey-of-learning-to-drive/202303242116962.jpeg new file mode 100644 index 000000000..6b32c75d3 Binary files /dev/null and b/posts/my-journey-of-learning-to-drive/202303242116962.jpeg differ diff --git a/posts/my-journey-of-learning-to-drive/202303242116962_hu12425904158888570193.webp b/posts/my-journey-of-learning-to-drive/202303242116962_hu12425904158888570193.webp new file mode 100644 index 000000000..96ec4e339 Binary files /dev/null and b/posts/my-journey-of-learning-to-drive/202303242116962_hu12425904158888570193.webp differ diff --git a/posts/my-journey-of-learning-to-drive/202303242116962_hu370609333924303518.jpeg b/posts/my-journey-of-learning-to-drive/202303242116962_hu370609333924303518.jpeg new file mode 100644 index 000000000..e4ec10098 Binary files /dev/null and b/posts/my-journey-of-learning-to-drive/202303242116962_hu370609333924303518.jpeg differ diff --git a/posts/my-journey-of-learning-to-drive/index.html b/posts/my-journey-of-learning-to-drive/index.html new file mode 100644 index 000000000..d20937fd7 --- /dev/null +++ b/posts/my-journey-of-learning-to-drive/index.html @@ -0,0 +1,95 @@ +我的学车之路 | 流动 +

      我的学车之路

      之前在2022年终总结提到过,我在练车考驾照。

      就在昨天,终于拿证了。👏👏👏

      咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶

      2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。
      +
      +2022年10月12日,科目一考试通过。
      +
      +2022年10月22日,科目二模拟驾驶。
      +
      +2022年11月13日,科目二第一次上车练习。
      +
      +2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。
      +
      +2023年2月4日,年后驾校恢复培训,继续科目二练车。
      +
      +2023年2月13日,科目二考试通过。
      +
      +2023年3月11日,科目三上路练习。
      +
      +2023年3月23日,上午科目三考试通过,下午科目四考试通过。
      +考试的时候,早上遇到临时交通管制,一直到9点40才开考。
      +考完回来,班车上的人说又管制不能考了。
      +班车拉回驾校,剩下的人中午加班考试。
      +

      终于不用再5点半起床赶班车了。🥱

      本本到手啦

      💬评论
      \ No newline at end of file diff --git a/posts/olympic-park-sunflower-tour/IMG_3492.JPG b/posts/olympic-park-sunflower-tour/IMG_3492.JPG new file mode 100644 index 000000000..2e2fe5c8d Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3492.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3492_hu11938434173603406964.webp b/posts/olympic-park-sunflower-tour/IMG_3492_hu11938434173603406964.webp new file mode 100644 index 000000000..cc52b434f Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3492_hu11938434173603406964.webp differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3492_hu15314692271159671672.JPG b/posts/olympic-park-sunflower-tour/IMG_3492_hu15314692271159671672.JPG new file mode 100644 index 000000000..ad4e23ff9 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3492_hu15314692271159671672.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3549.JPG b/posts/olympic-park-sunflower-tour/IMG_3549.JPG new file mode 100644 index 000000000..ee4213179 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3549.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3549_hu10174327908211758265.webp b/posts/olympic-park-sunflower-tour/IMG_3549_hu10174327908211758265.webp new file mode 100644 index 000000000..6b4207a90 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3549_hu10174327908211758265.webp differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3549_hu6048251454723091930.JPG b/posts/olympic-park-sunflower-tour/IMG_3549_hu6048251454723091930.JPG new file mode 100644 index 000000000..d7f4b5733 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3549_hu6048251454723091930.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3556.JPG b/posts/olympic-park-sunflower-tour/IMG_3556.JPG new file mode 100644 index 000000000..c5499f667 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3556.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3556_hu16432577379610021050.JPG b/posts/olympic-park-sunflower-tour/IMG_3556_hu16432577379610021050.JPG new file mode 100644 index 000000000..7a33ca6bb Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3556_hu16432577379610021050.JPG differ diff --git a/posts/olympic-park-sunflower-tour/IMG_3556_hu17063267086198247318.webp b/posts/olympic-park-sunflower-tour/IMG_3556_hu17063267086198247318.webp new file mode 100644 index 000000000..42bc7bf46 Binary files /dev/null and b/posts/olympic-park-sunflower-tour/IMG_3556_hu17063267086198247318.webp differ diff --git a/posts/olympic-park-sunflower-tour/index.html b/posts/olympic-park-sunflower-tour/index.html new file mode 100644 index 000000000..e84db96c1 --- /dev/null +++ b/posts/olympic-park-sunflower-tour/index.html @@ -0,0 +1,18 @@ +奥林匹克公园向日葵之旅 | 流动 +

      奥林匹克公园向日葵之旅

      媳妇有事回老家了,这两天自己带娃。

      小区群里有人说奥林匹克公园的向日葵开了,适合拍照。

      正好周六多云,没有太阳,出门遛娃。

      带上我好久不用的相机,省得发霉了。

      以往都是去的南园,第一次来北园。

      西门进入,沿着路往右走,一会就到。

      人很多,估计大家都因为之前疫情在家憋疯了。

      到了没多久,太阳又出来了,超级晒。

      提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。

      向日葵 +向日葵 +荷花 +荷叶上的蜻蜓

      💬评论
      \ No newline at end of file diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817.jpg" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817.jpg" new file mode 100644 index 000000000..3ec34af0d Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817.jpg" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu13713730314974011965.jpg" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu13713730314974011965.jpg" new file mode 100644 index 000000000..5d4d56a2c Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu13713730314974011965.jpg" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu16491399018090528343.jpg" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu16491399018090528343.jpg" new file mode 100644 index 000000000..b8fdc54c8 Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu16491399018090528343.jpg" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu17796839330364625103.webp" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu17796839330364625103.webp" new file mode 100644 index 000000000..6d8e6cc2b Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu17796839330364625103.webp" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu18376626475818783202.webp" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu18376626475818783202.webp" new file mode 100644 index 000000000..012fb10be Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu18376626475818783202.webp" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu8408749300656451061.jpg" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu8408749300656451061.jpg" new file mode 100644 index 000000000..5582f5586 Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu8408749300656451061.jpg" differ diff --git "a/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu9590159396780233545.webp" "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu9590159396780233545.webp" new file mode 100644 index 000000000..32cab4f9a Binary files /dev/null and "b/posts/olympic-park-sunflower-tour/\345\276\256\344\277\241\345\233\276\347\211\207_20220725183817_hu9590159396780233545.webp" differ diff --git a/posts/optimize-google-analytics/gtmetrix-report.png b/posts/optimize-google-analytics/gtmetrix-report.png new file mode 100644 index 000000000..4df2518a2 Binary files /dev/null and b/posts/optimize-google-analytics/gtmetrix-report.png differ diff --git a/posts/optimize-google-analytics/gtmetrix-report_hu7862777591110582981.webp b/posts/optimize-google-analytics/gtmetrix-report_hu7862777591110582981.webp new file mode 100644 index 000000000..d886bfa96 Binary files /dev/null and b/posts/optimize-google-analytics/gtmetrix-report_hu7862777591110582981.webp differ diff --git a/posts/optimize-google-analytics/gtmetrix-report_hu9813378589716916173.png b/posts/optimize-google-analytics/gtmetrix-report_hu9813378589716916173.png new file mode 100644 index 000000000..04c7ee8e7 Binary files /dev/null and b/posts/optimize-google-analytics/gtmetrix-report_hu9813378589716916173.png differ diff --git a/posts/optimize-google-analytics/index.html b/posts/optimize-google-analytics/index.html new file mode 100644 index 000000000..49f928ab7 --- /dev/null +++ b/posts/optimize-google-analytics/index.html @@ -0,0 +1,102 @@ +加速Google Analytics | 流动 +

      加速Google Analytics

      起因

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      优化

      1. 访问加速

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      Cloudflare新建Worker,代码如下,保存后部署。

      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      通过性能分析,发现js文件过大,影响页面加载速度。

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      搜索一番,找到一个瘦身版Google Analytics

      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      💬评论
      \ No newline at end of file diff --git a/posts/page/1/index.html b/posts/page/1/index.html new file mode 100644 index 000000000..6b6bf6ad8 --- /dev/null +++ b/posts/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/posts/ + \ No newline at end of file diff --git a/posts/page/2/index.html b/posts/page/2/index.html new file mode 100644 index 000000000..1d4ef7796 --- /dev/null +++ b/posts/page/2/index.html @@ -0,0 +1,69 @@ +文章 | 流动 +

      利用Github Actions定时抓取微博

      背景 在微博上关注了一些用户,比如tk教主,月风。 +但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。 +实现方案 整体思路:利用Github Actions的Scheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。 +...

      2023-10-07 · 2 min · 823 words · Liudon

      北大口腔牙周刮治记录

      病情 上次洗完牙后,还是不时有出血的情况。 +前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。 +于是,又跑到医院来看牙了。 +医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。 +...

      2023-09-17 · 2 min · 716 words · Liudon

      故乡回忆之旅

      赶在8月底,趁着娃暑假的尾声,回了趟老家。 +老家有条俗语,“永福庄的街,三里长”。 +这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。 +小时候,整天在这条街上跑来跑去。 +...

      2023-09-09 · 1 min · 292 words · Liudon

      解决Golang使用go get安装包后找不到可执行文件的问题

      背景 编译流水线代码 +go get google.golang.org/protobuf/cmd/protoc-gen-go@latest protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto ... go build -o xxx 在go升级到1.20.1版本后,执行报错。 +protoc-gen-go: program not found or is not executable 解决 Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead. +In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled. +...

      2023-08-17 · 1 min · 195 words · Liudon

      修正Hugo的JSON Feed格式

      问题背景 前几天在Planet里follow自己的web3博客,遇到下面的错误。 +经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。 +因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。 +[ { "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n", "permalink": "https://dvel.me/posts/chd-quiz-answer/", "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!", "title": "CHD 油猴脚本:每日签到自动答题" }, ... ] 下面是一个JSON Feed的示例,详细规范见jsonfeed.org。 +...

      2023-03-25 · 3 min · 1451 words · Liudon

      我的学车之路

      之前在2022年终总结提到过,我在练车考驾照。 +就在昨天,终于拿证了。👏👏👏 +咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶 +2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 2022年10月12日,科目一考试通过。 2022年10月22日,科目二模拟驾驶。 2022年11月13日,科目二第一次上车练习。 2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 2023年2月4日,年后驾校恢复培训,继续科目二练车。 2023年2月13日,科目二考试通过。 2023年3月11日,科目三上路练习。 2023年3月23日,上午科目三考试通过,下午科目四考试通过。 考试的时候,早上遇到临时交通管制,一直到9点40才开考。 考完回来,班车上的人说又管制不能考了。 班车拉回驾校,剩下的人中午加班考试。 终于不用再5点半起床赶班车了。🥱 +...

      2023-03-24 · 1 min · 375 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon

      新冠疫情后的第一个春节

      下面的内容是由chatGPT润色生成的。 +AI太强大了 😂 +当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。 +但我从未想过,等我长大后,我也会成为其中的一员。 +...

      2023-02-16 · 1 min · 242 words · Liudon

      第一次清理键盘

      19年生日的时候,媳妇送了一款flico的机械键盘。 +这次搬家后,想着年前清理下键盘,实在是太脏了。 +周五下班,带上键盘回家。 +...

      2023-01-16 · 1 min · 141 words · Liudon

      2022年终总结

      2022年已经过去1周多了,记录一下我的2022年。 +疫情 2022年,是新冠疫情的第三年,也是切身感受到的一年。 +3月22日晚,8点半和同事刚上13号线地铁。 +...

      2023-01-12 · 2 min · 646 words · Liudon

      去掉Cloudflare烦人的email-decode.min.js请求

      通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。 +...

      2022-08-26 · 1 min · 180 words · Liudon
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高. +问题表现 7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。 +偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。 +...

      2022-08-20 · 2 min · 886 words · Liudon

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      向日葵

      向日葵

      奥林匹克公园向日葵之旅

      媳妇有事回老家了,这两天自己带娃。 +小区群里有人说奥林匹克公园的向日葵开了,适合拍照。 +正好周六多云,没有太阳,出门遛娃。 +带上我好久不用的相机,省得发霉了。 +...

      2022-07-21 · 1 min · 179 words · Liudon

      记第二次洗牙

      最近刷牙的时候,牙龈总是出血。 +距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。 +上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。 +...

      2022-06-21 · 1 min · 283 words · Liudon

      记录2022年海淀幼升小

      18年的热点新闻,纳税千万孩子无法在北京上学。 +一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。 +...

      2022-05-25 · 2 min · 715 words · Liudon

      Golang解析json的一个问题

      业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题: +请求接口,按返回包字段判断请求成功与否。 伪代码如下: +package main import ( "encoding/json" "fmt" ) type Response struct { Code int `json:"code"` Msg string `json:"msg"` } func main() { // 场景1,返回包符合接口要求 str := `{"code":100,"msg":"failed"}` var res Response json.Unmarshal([]byte(str), &res) fmt.Printf("res=%+v\n", res) // 解析正确,符合预期 // res={Code:100 Msg:failed} // 场景2,返回包不符合接口要求,缺少相关字段 str = `{"retCode":100,"retMsg":"failed"}` var res1 Response json.Unmarshal([]byte(str), &res1) fmt.Printf("res=%+v\n", res1) // 解析错误,不符合预期 // res={Code:0 Msg:} } 这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。 +...

      2022-05-20 · 1 min · 424 words · Liudon

      疫情下的生活

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。 +昨天看新闻,基本没有社会面新增了,感觉要解封了。 +没想到今天直接被打脸,封控升级了。 +...

      2022-05-20 · 1 min · 189 words · Liudon

      整理下博客的一些调整

      新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。 +更新主题版本,展示文章tag标签 通过对比主题作者的网站,发现使用的不是最新代码。 +...

      2022-05-13 · 1 min · 331 words · Liudon
      \ No newline at end of file diff --git a/posts/page/3/index.html b/posts/page/3/index.html new file mode 100644 index 000000000..ada713ae4 --- /dev/null +++ b/posts/page/3/index.html @@ -0,0 +1,68 @@ +文章 | 流动 +

      疫情下的五一假期

      五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。 +当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。 +说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。 +...

      2022-05-05 · 1 min · 431 words · Liudon

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon

      二刷百望山

      又是周末,娃约了小伙伴一起爬山。 +百望山,二刷走起。 +约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。 +出门晚了,还打不到车,快10点才到。 +小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。 +...

      2022-04-17 · 1 min · 209 words · Liudon

      带娃游颐和园

      上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。 +周六7点准时起床,得早点去省得人多排队。 +8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。 +...

      2022-04-11 · 1 min · 258 words · Liudon

      博客架构说明

      在拿到liudon.com域名前,手中已有两个域名: +liudon.org liudon.xyz liudon.org已经不再更新,仅作归档使用。 liudon.xyz当时是静态博客流行,尝鲜使用。 +...

      2022-04-10 · 1 min · 433 words · Liudon

      难得的清明假期

      前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。 +趁着这次难得的假期,外出放松一下。 +爬百望山。 +娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。 +...

      2022-04-06 · 1 min · 343 words · Liudon

      十一年的等待,终于拿到了liudon.com域名

      在关于部分,有写域名的来历。 +当时liudon.com已经被注册,所以只好注册了liudon.org。 +2011年注册的liudon.org,最早用wordpress搭建了博客。 +...

      2022-04-01 · 2 min · 588 words · Liudon

      被隔离的一周

      从没有想过疫情会离自己这么近,记录一下。 +周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。 +周二早上全员核酸阴性,继续到公司上班。 +...

      2022-04-01 · 1 min · 328 words · Liudon

      mysql中字符串和整型自动转换的问题

      表结构如下 +desc info; +-------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------+------+-----+---------+----------------+ | id | int(8) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | +-------+-----------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) 执行sql. +insert into info values ('', 'xxx'); insert into info values ('', 'yyy'); 查询记录. +select * from info; +----+------+ | id | name | +----+------+ | 1 | xxx | | 2 | yyy | +----+------+ 2 rows in set (0.00 sec) 执行下面sql. +...

      2020-12-14 · 1 min · 303 words · Liudon

      一次惊心动魄的Mysql更新操作

      问题描述 # 表结构 MySQL > desc user_packages; +----------------+---------------------+------+-----+---------------------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+---------------------+------+-----+---------------------+----------------+ | up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | start_date | date | NO | | NULL | | | end_date | date | NO | | NULL | | | up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | | up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +----------------+---------------------+------+-----+---------------------+----------------+ 5 rows in set (0.00 sec) MySQL > select * from user_packages limit 5; +-------+------------+------------+ | up_id | start_date | end_date | +-------+------------+------------+ | 185 | 2018-04-01 | 2018-06-30 | | 186 | 2018-04-01 | 2018-06-30 | | 187 | 2018-04-01 | 2018-06-30 | | 188 | 2018-04-01 | 2018-06-30 | | 189 | 2018-04-01 | 2018-06-30 | +-------+------------+------------+ 5 rows in set (0.00 sec) 操作过程 需要更新某条记录的end_date字段,执行sql如下: +...

      2020-05-19 · 2 min · 776 words · Liudon

      如何在北京公积金网站上修改婚姻状况

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告 +时间:2020年01月08日 +来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html +...

      2020-01-17 · 1 min · 438 words · Liudon

      PHP7.2编译安装后没有php.ini文件的问题

      下载PHP7.2源码,编译安装。 +[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies [root@VM_73_135_centos ~/swoole-src-4.4.12]# 安装Swoole。 +phpize && \ ./configure && \ make && make install 安装完,准备修改php.ini文件,结果没找到。 +...

      2019-11-26 · 1 min · 241 words · Liudon

      检测网站支持的SSL/TLS协议版本

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。 +为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。 +...

      2019-11-14 · 1 min · 205 words · Liudon

      记一次难忘的手术经历

      俗话说的好,十人九痔。这九个人里就有我一个。 😂 +去年因为痔疮去过一趟医院,医生当时建议手术。 +后来用了点药,没啥症状了,就不放在心上了。 +结果前两天公司团建吃了点辣,痔疮又犯了,大便拉不出来,憋的难受。 +...

      2019-10-28 · 2 min · 646 words · Liudon

      十一假期经历

      今年的十一火车票非常难抢,12306的候补订单,一直等到时间截止也没订上票。 +只好请了2天假,提前回家了,给自己也放个假休息一下。 +回家的几个经历: +家里的老洗衣机光荣退休了,年龄比我都要大,爸妈舍不得换一直用着。 +...

      2019-10-08 · 1 min · 306 words · Liudon

      Swoft 框架运行分析(五) —— ConsoleProcessor模块分析

      这里以Swoft启动http server为例。 +php bin/swoft http:start +执行上述命令,启动http server。 +在前面第一篇文章的时候,提到了如何启动http服务。 +今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。 +...

      2019-09-26 · 10 min · 4524 words · Liudon

      Swoft 框架运行分析(四) —— EventProcessor模块分析

      今天我们来看一下EventProcessor的实现。 +/** * Handle event register * @return bool */ public function handle(): bool { if (!$this->application->beforeEvent()) { CLog::warning('Stop event processor by beforeEvent return false'); return false; } /** @var EventManager $eventManager */ $eventManager = bean('eventManager'); [$count1, $count2] = ListenerRegister::register($eventManager); CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2); // Trigger a app init event Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); return $this->application->afterEvent(); } 获取eventManager的Bean实例,对应为Swoft\Event\Manager\EventManager类。 +...

      2019-09-26 · 4 min · 1896 words · Liudon

      一个git submodule update引发的问题

      背景 1月份的时候,用hugo搭了这套博客系统。 +本机写md文件,更新到github,然后通过travis-ci自动发布。 +jane主题是通过git submodule引入的,.gitmodules文件内容。 +...

      2019-09-06 · 4 min · 1748 words · Liudon

      一个Curl的耗时长的问题

      发现某个接口请求很慢,但是后端确认接口是很快的。 +在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。 +业务里用到了Requests这个库,一开始以为是这个库导致的问题。 +...

      2019-09-04 · 2 min · 925 words · Liudon

      Swoft 框架运行分析(三) —— BeanProcessor模块分析

      今天讲一下BeanProcessor模块,先看一下handle方法实现。 +/** * Handle bean * * @return bool * @throws ReflectionException * @throws AnnotationException */ public function handle(): bool { if (!$this->application->beforeBean()) { return false; } $handler = new BeanHandler(); $definitions = $this->getDefinitions(); $parsers = AnnotationRegister::getParsers(); $annotations = AnnotationRegister::getAnnotations(); BeanFactory::addDefinitions($definitions); BeanFactory::addAnnotations($annotations); BeanFactory::addParsers($parsers); BeanFactory::setHandler($handler); BeanFactory::init(); /* @var Config $config*/ $config = BeanFactory::getBean('config'); CLog::info('config path=%s', $config->getPath()); CLog::info('config env=%s', $config->getEnv()); $stats = BeanFactory::getStats(); CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats)); return $this->application->afterBean(); } 先通过getDefinitions方法获取所有的Bean定义。 +...

      2019-09-02 · 9 min · 4044 words · Liudon
      \ No newline at end of file diff --git a/posts/page/4/index.html b/posts/page/4/index.html new file mode 100644 index 000000000..1b1724a57 --- /dev/null +++ b/posts/page/4/index.html @@ -0,0 +1,20 @@ +文章 | 流动 +

      Swoft 框架运行分析(二) —— AnnotationProcessor模块分析

      上一篇介绍了,SwoftApplication里定义了6个Processor对象。 +protected function processors(): array { return [ new EnvProcessor($this), new ConfigProcessor($this), new AnnotationProcessor($this), new BeanProcessor($this), new EventProcessor($this), new ConsoleProcessor($this), ]; } 所有的Processor实现都在framework\src\Processor目录下。 +...

      2019-08-29 · 4 min · 1656 words · Liudon

      Swoft 框架运行分析(一)

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。 +以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。 +刚开始看的时候,感觉自己像个原始人,完全看不懂。 +...

      2019-08-29 · 2 min · 966 words · Liudon

      BCMath 与 科学计数

      代码如下 +<?php echo 9.99997600 + 2.4E-5; echo "\n===\n"; echo bcadd(9.99997600, 2.4E-5, 8); 结果为 +10 === 9.99997600 问了朋友,查了各种资料,终于在PHP手册里发现了这段话。 +Caution Passing values of type float to a BCMath function which expects a string as operand may not have the desired effect due to the way PHP converts float values to string, namely that the string may be in exponential notation (which is not supported by BCMath), and that the decimal separator is locale dependent (while BCMath always expects a decimal point). +...

      2019-08-16 · 1 min · 168 words · Liudon

      Flink Could Not Resolve Resourcemanager Address

      什么是Flink。 +Apache Flink® - Stateful Computations over Data Streams +Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。 +...

      2019-03-28 · 2 min · 517 words · Liudon

      解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错

      今天安装hugofy的包时,一直遇到"There Are No Packages Available for Installation"的错误。 按网上的教程,配置host,配置代理都不起作用。 +...

      2019-01-11 · 1 min · 336 words · Liudon

      2019,新开始

      从2011年开始写博客,博客程序从WordPress换成Typecho。 早就有想法换成静态博客,一直没时间搞。 +2019年了,新年新气象,用hugo + github pages搞了个新博客。 +...

      2019-01-09 · 1 min · 250 words · Liudon
      \ No newline at end of file diff --git "a/posts/php7.2\347\274\226\350\257\221\345\256\211\350\243\205\345\220\216\346\262\241\346\234\211php.ini\346\226\207\344\273\266/index.html" "b/posts/php7.2\347\274\226\350\257\221\345\256\211\350\243\205\345\220\216\346\262\241\346\234\211php.ini\346\226\207\344\273\266/index.html" new file mode 100644 index 000000000..012392476 --- /dev/null +++ "b/posts/php7.2\347\274\226\350\257\221\345\256\211\350\243\205\345\220\216\346\262\241\346\234\211php.ini\346\226\207\344\273\266/index.html" @@ -0,0 +1,57 @@ +PHP7.2编译安装后没有php.ini文件的问题 | 流动 +

      PHP7.2编译安装后没有php.ini文件的问题

      下载PHP7.2源码,编译安装。

      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v
      +PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )
      +Copyright (c) 1997-2018 The PHP Group
      +Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]# 
      +

      安装Swoole。

      phpize && \
      +./configure && \
      +make && make install
      +

      安装完,准备修改php.ini文件,结果没找到。

      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll /usr/local/services/php/etc/
      +total 88
      +-rw-r--r-- 1 root root  1364 Nov 26 19:34 pear.conf
      +-rw-r--r-- 1 root root  4508 Nov 26 19:34 php-fpm.conf.default
      +drwxr-xr-x 2 root root  4096 Nov 26 19:34 php-fpm.d
      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => (none)
      +

      这是什么鬼,居然没有php.ini文件。

      原来PHP源码里提供了两个php.ini文件,你需要按需拷贝到你的PHP的目录下。

      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll ../php-7.2.25 | grep ini
      +-rw-rw-r--  1 root root   71232 Nov 20 23:11 php.ini-development
      +-rw-rw-r--  1 root root   71413 Nov 20 23:11 php.ini-production
      +

      拷贝后。

      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => /usr/local/services/php/etc/php.ini
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]#
      +
      💬评论
      \ No newline at end of file diff --git a/posts/remove-cloudflares-email-decode.min.js/ScrapeShield.png b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield.png new file mode 100644 index 000000000..084ed8f68 Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield.png differ diff --git a/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu1343187000676483553.webp b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu1343187000676483553.webp new file mode 100644 index 000000000..3c5495dfb Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu1343187000676483553.webp differ diff --git a/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu3229372373758113083.png b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu3229372373758113083.png new file mode 100644 index 000000000..6dc4edbae Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/ScrapeShield_hu3229372373758113083.png differ diff --git a/posts/remove-cloudflares-email-decode.min.js/index.html b/posts/remove-cloudflares-email-decode.min.js/index.html new file mode 100644 index 000000000..4fdf35ce4 --- /dev/null +++ b/posts/remove-cloudflares-email-decode.min.js/index.html @@ -0,0 +1,6 @@ +去掉Cloudflare烦人的email-decode.min.js请求 | 流动 +

      去掉Cloudflare烦人的email-decode.min.js请求

      通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。

      webpagetest测试结果

      看路径像是Cloudflare的文件,搜了下主题代码,没找到相关文件。

      经过一番搜索,原来这个是Cloudflare的电子邮件地址混淆技术功能。

      Scrape Shield

      关闭这个功能即可。

      💬评论
      \ No newline at end of file diff --git a/posts/remove-cloudflares-email-decode.min.js/webpagetest.png b/posts/remove-cloudflares-email-decode.min.js/webpagetest.png new file mode 100644 index 000000000..399dbbf63 Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/webpagetest.png differ diff --git a/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu13368857670247743502.png b/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu13368857670247743502.png new file mode 100644 index 000000000..b2cd5bab9 Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu13368857670247743502.png differ diff --git a/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu17128824884922281865.webp b/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu17128824884922281865.webp new file mode 100644 index 000000000..c86777b72 Binary files /dev/null and b/posts/remove-cloudflares-email-decode.min.js/webpagetest_hu17128824884922281865.webp differ diff --git a/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154.png b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154.png new file mode 100644 index 000000000..16c65fee5 Binary files /dev/null and b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154.png differ diff --git a/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu3099176474927034.webp b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu3099176474927034.webp new file mode 100644 index 000000000..3b7e1c16c Binary files /dev/null and b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu3099176474927034.webp differ diff --git a/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu9494551717517101117.png b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu9494551717517101117.png new file mode 100644 index 000000000..82ce55e1c Binary files /dev/null and b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/20240522210154_hu9494551717517101117.png differ diff --git a/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/index.html b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/index.html new file mode 100644 index 000000000..3dc63de52 --- /dev/null +++ b/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/index.html @@ -0,0 +1,114 @@ +搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway | 流动 +

      搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

      背景

      4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。

      没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。

      但还是无法访问,被Cloudflare拦截了。

      Error 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC
      +CNAME Cross-User Banned
      +What happened?
      +You've requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare's security policy.
      +
      +What can I do?
      +If this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2's documentation for details.
      +
      +
      +Visit our website to learn more about Cloudflare.
      +

      这周在Discord群里,看到有人发消息,说是Cloudflare将下线IPFS Gateway网关服务。

      https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard

      All traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!

      方案调研

      经过一番搜索,找到了一篇自建IPFS Gateway网关的资料,里面用到了bifrost-gateway组件。

      To run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:
      +
      +$ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
      +

      看文档,可以通过这个命令搭建一个自己的网关服务,同时支持DNSLink方式访问。

      太棒了,感觉可以自己搭一套网关,然后用Nginx反代对外提供服务。

      在之前将博客部署到星际文件系统(IPFS)文章中,已经通过Kubo搭建了一套本地IPFS服务。

      上机器验证一下可行性:

      1. 启动Bifrost Gateway,网关默认地址为https://127.0.0.1:8081

        $ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
        +2024/05/22 20:54:00 Starting bifrost-gateway dev-build
        +2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080
        +2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024
        +2024/05/22 20:54:00 GRAPH_BACKEND: false
        +2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001
        +2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081
        +2024/05/22 20:54:00   Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
        +2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081
        +2024/05/22 20:54:00   Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/
        +2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus
        +
      2. 在另外一个终端下,执行命令

        $ curl 'http://127.0.0.1:8081/' -H"Host:liudon.xyz" -I
        +HTTP/1.1 200 OK
        +Accept-Ranges: bytes
        +Access-Control-Allow-Headers: Content-Type
        +Access-Control-Allow-Headers: Range
        +Access-Control-Allow-Headers: User-Agent
        +Access-Control-Allow-Headers: X-Requested-With
        +Access-Control-Allow-Methods: GET
        +Access-Control-Allow-Methods: HEAD
        +Access-Control-Allow-Methods: OPTIONS
        +Access-Control-Allow-Origin: *
        +Access-Control-Expose-Headers: Content-Length
        +Access-Control-Expose-Headers: Content-Range
        +Access-Control-Expose-Headers: X-Chunked-Output
        +Access-Control-Expose-Headers: X-Ipfs-Path
        +Access-Control-Expose-Headers: X-Ipfs-Roots
        +Access-Control-Expose-Headers: X-Stream-Output
        +Content-Length: 26283
        +Content-Type: text/html
        +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
        +Last-Modified: Wed, 22 May 2024 12:57:29 GMT
        +X-Ipfs-Path: /ipns/liudon.xyz/
        +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
        +Date: Wed, 22 May 2024 12:57:29 GMT
        +

      验证可行,不过我记得Kubo默认就有网关服务的,试一下直接通过Kubo默认网关的情况。

      Kubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用

      $ curl 'http://127.0.0.1:8080/' -H"Host:liudon.xyz" -I
      +HTTP/1.1 200 OK
      +Accept-Ranges: bytes
      +Access-Control-Allow-Headers: Content-Type
      +Access-Control-Allow-Headers: Range
      +Access-Control-Allow-Headers: User-Agent
      +Access-Control-Allow-Headers: X-Requested-With
      +Access-Control-Allow-Methods: GET
      +Access-Control-Allow-Origin: *
      +Access-Control-Expose-Headers: Content-Length
      +Access-Control-Expose-Headers: Content-Range
      +Access-Control-Expose-Headers: X-Chunked-Output
      +Access-Control-Expose-Headers: X-Ipfs-Path
      +Access-Control-Expose-Headers: X-Ipfs-Roots
      +Access-Control-Expose-Headers: X-Stream-Output
      +Content-Length: 26283
      +Content-Type: text/html
      +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
      +Last-Modified: Wed, 22 May 2024 12:59:25 GMT
      +X-Ipfs-Path: /ipns/liudon.xyz/
      +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
      +Date: Wed, 22 May 2024 12:59:25 GMT
      +

      也是可以的,那就没必要多搞一套bifrost网关了。

      具体实现

      通过Nginx反向代理转发到本地IPFS网关,只需要改一下解析就可以继续使用IPFS服务了。

      方案

      1. Nginx反向代理
      server {
      +    listen 443 ssl http2;
      +    server_name liudon.xyz;
      +
      +    ssl_certificate /etc/nginx/ssl/liudon.xyz/fullchain.cer;
      +    ssl_certificate_key /etc/nginx/ssl/liudon.xyz/liudon.xyz.key;
      +
      +    ssl_protocols TLSv1.2 TLSv1.3;
      +    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
      +    ssl_prefer_server_ciphers on;
      +    ssl_session_cache shared:SSL:10m;
      +    ssl_session_timeout 10m;
      +
      +    location / {
      +            proxy_pass http://127.0.0.1:8080;
      +            proxy_set_header Host $host; // 注意这里要传递反代的域名信息,限制只能访问我们自己dnslink对应的资源
      +    }
      +
      +    access_log /var/log/nginx/liudon.xyz.access.log;
      +    error_log /var/log/nginx/liudon.xyz.error.log;
      +}
      +

      申请Let's Encrypt证书,证书相关的就不多做介绍了,网上资料很多。

      1. 更改DNS解析
      原有的解析
      +
      +类型:CNAME
      +名称:liudon.xyz
      +内容:cloudflare-ipfs.com
      +
      +新的解析
      +
      +类型:A
      +名称:liudon.xyz
      +内容:你的服务器公网IP
      +

      搞定,又可以继续白嫖IPFS服务了。

      💬评论
      \ No newline at end of file diff --git a/posts/responsive-and-optimized-images-with-hugo/cover.png b/posts/responsive-and-optimized-images-with-hugo/cover.png new file mode 100644 index 000000000..70e1a45fd Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/cover.png differ diff --git a/posts/responsive-and-optimized-images-with-hugo/cover_hu16575402247833177964.webp b/posts/responsive-and-optimized-images-with-hugo/cover_hu16575402247833177964.webp new file mode 100644 index 000000000..4549532d1 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/cover_hu16575402247833177964.webp differ diff --git a/posts/responsive-and-optimized-images-with-hugo/cover_hu4064065245092380092.png b/posts/responsive-and-optimized-images-with-hugo/cover_hu4064065245092380092.png new file mode 100644 index 000000000..6404d44f5 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/cover_hu4064065245092380092.png differ diff --git a/posts/responsive-and-optimized-images-with-hugo/figure.png b/posts/responsive-and-optimized-images-with-hugo/figure.png new file mode 100644 index 000000000..36426d91e Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/figure.png differ diff --git a/posts/responsive-and-optimized-images-with-hugo/figure_hu13810583128976659866.webp b/posts/responsive-and-optimized-images-with-hugo/figure_hu13810583128976659866.webp new file mode 100644 index 000000000..7c813c976 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/figure_hu13810583128976659866.webp differ diff --git a/posts/responsive-and-optimized-images-with-hugo/figure_hu15918180951896956988.png b/posts/responsive-and-optimized-images-with-hugo/figure_hu15918180951896956988.png new file mode 100644 index 000000000..bf173a626 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/figure_hu15918180951896956988.png differ diff --git a/posts/responsive-and-optimized-images-with-hugo/index.html b/posts/responsive-and-optimized-images-with-hugo/index.html new file mode 100644 index 000000000..f7defb4e2 --- /dev/null +++ b/posts/responsive-and-optimized-images-with-hugo/index.html @@ -0,0 +1,226 @@ +使用Hugo实现响应式和优化的图片 | 流动 +

      使用Hugo实现响应式和优化的图片

      继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

      问题

      在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

      经过一段运行后,发现这里有一个弊端。

      Run hugo --gc --minify --cleanDestinationDir
      +Start building sites … 
      +hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio
      +
      +ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
      +ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer
      +

      随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

      失败的时候,需要手动重跑构建,自动化发布卡壳了。

      优化

      经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

      Image processing

      Resize, crop, rotate, filter, and convert images.

      https://gohugo.io/content-management/image-processing/

      下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

      image processing需要用到Page bundles,所以文章目录结构需要调整, +将一篇文章的资源(md文件,图片等)放在一个目录下。

      content/
      +├── posts
      +│   ├── my-post
      +│   │   ├── content1.md
      +│   │   ├── content2.md
      +│   │   ├── image1.jpg
      +│   │   ├── image2.png
      +│   │   └── index.md
      +│   └── my-other-post
      +│       └── index.md
      +

      目录结构调整完毕后,接下来修改图片显示文件代码。

      这里需要生成webp格式图片,所以需要使用hugo的extended版本

      PagerMod主题涉及到图片显示的一共三个文件:

      • _default/_markup/render-image.html,对应markdown图片语法解析。

        {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +{{ with $src := .Page.Resources.GetMatch .Destination }}
        +    {{- if $responsiveImages -}}
        +        <picture>
        +            /*只有使用了hugo扩展版本的,才生成webp格式图片*/
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +            <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +        </picture>
        +    {{- else }}
        +        <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +    {{- end }}
        +{{ end }}
        +
      • partials/cover.html,对应文章封面解析。

        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    /*封面响应式图片配置开关,默认为true*/
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- if $responsiveImages -}}
        +        <picture>
        +        {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
        +        <source type="image/webp" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +            {{- end -}}
        +        {{- end -}}" sizes="{{ $dataSzes }}" />
        +        {{- end -}}
        +        <source type="{{ $cover.MediaType.Type }}" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if ge $cover.Width . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}
        +        {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
        +
        +        <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        </picture>
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      • shortcodes/figure.html,对应文章内figure语法解析。

        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{ $src := .Get "src" }}
        +{{ $align := .Get "align" }}
        +{{ $alt := .Get "alt" }}
        +{{ $caption := .Get "caption" }}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
        +        {{- if eq (.Get "align") "center" }}align-center {{ end }}
        +        {{- with .Get "class" }}{{ . }}{{- end }}"
        +{{- end -}}>
        +    {{- if .Get "link" -}}
        +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        +    {{- end }}
        +    {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        +    <picture>
        +        {{- if $responsiveImages -}}
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +        {{- end }}
        +        <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
        +        {{- if or ($alt) ($caption) }}
        +        alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
        +        {{- end -}}
        +        {{- with $src.Width -}} width="{{ . }}"{{- end -}}
        +        {{- with $src.Height -}} height="{{ . }}"{{- end -}}
        +        /> <!-- Closing img tag -->
        +    </picture>
        +    {{ end }}
        +    {{- if .Get "link" }}</a>{{ end -}}
        +    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        +        <figcaption>
        +            {{ with (.Get "title") -}}
        +                {{ . }}
        +            {{- end -}}
        +            {{- if or (.Get "caption") (.Get "attr") -}}<p>
        +                {{- .Get "caption" | markdownify -}}
        +                {{- with .Get "attrlink" }}
        +                    <a href="{{ . }}">
        +                {{- end -}}
        +                {{- .Get "attr" | markdownify -}}
        +                {{- if .Get "attrlink" }}</a>{{ end }}</p>
        +            {{- end }}
        +        </figcaption>
        +    {{- end }}
        +</figure>
        +

      使用效果

      正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

      • markdown图片显示

      render-image

      • figure短代码显示

      figure

      • 封面显示

      cover

      提示:

      随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

      Start building sites  
      +hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
      +Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
      +Built in 22356 ms
      +

      可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

      buildDrafts: false
      +buildFuture: false
      +buildExpired: false
      +
      +timeout: 60s // 调大此处的时间即可
      +

      终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。

      💬评论
      \ No newline at end of file diff --git a/posts/responsive-and-optimized-images-with-hugo/render-image.png b/posts/responsive-and-optimized-images-with-hugo/render-image.png new file mode 100644 index 000000000..ebfb9ed1e Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/render-image.png differ diff --git a/posts/responsive-and-optimized-images-with-hugo/render-image_hu10785117163759029787.webp b/posts/responsive-and-optimized-images-with-hugo/render-image_hu10785117163759029787.webp new file mode 100644 index 000000000..5b64bdd47 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/render-image_hu10785117163759029787.webp differ diff --git a/posts/responsive-and-optimized-images-with-hugo/render-image_hu6098837098373363195.png b/posts/responsive-and-optimized-images-with-hugo/render-image_hu6098837098373363195.png new file mode 100644 index 000000000..c8c9c4876 Binary files /dev/null and b/posts/responsive-and-optimized-images-with-hugo/render-image_hu6098837098373363195.png differ diff --git a/posts/review-2022/202301161440442.png b/posts/review-2022/202301161440442.png new file mode 100644 index 000000000..833fe2c3e Binary files /dev/null and b/posts/review-2022/202301161440442.png differ diff --git a/posts/review-2022/202301161440442_hu4648330161098337466.png b/posts/review-2022/202301161440442_hu4648330161098337466.png new file mode 100644 index 000000000..adf252a29 Binary files /dev/null and b/posts/review-2022/202301161440442_hu4648330161098337466.png differ diff --git a/posts/review-2022/202301161440442_hu6661987352865925613.webp b/posts/review-2022/202301161440442_hu6661987352865925613.webp new file mode 100644 index 000000000..3c6cee354 Binary files /dev/null and b/posts/review-2022/202301161440442_hu6661987352865925613.webp differ diff --git a/posts/review-2022/20230216210442.jpg b/posts/review-2022/20230216210442.jpg new file mode 100644 index 000000000..1a65f8159 Binary files /dev/null and b/posts/review-2022/20230216210442.jpg differ diff --git a/posts/review-2022/20230221153216.jpg b/posts/review-2022/20230221153216.jpg new file mode 100644 index 000000000..20c2ab912 Binary files /dev/null and b/posts/review-2022/20230221153216.jpg differ diff --git a/posts/review-2022/20230221153216_hu16647349186155928729.webp b/posts/review-2022/20230221153216_hu16647349186155928729.webp new file mode 100644 index 000000000..943632f31 Binary files /dev/null and b/posts/review-2022/20230221153216_hu16647349186155928729.webp differ diff --git a/posts/review-2022/20230221153216_hu4944469646759786940.jpg b/posts/review-2022/20230221153216_hu4944469646759786940.jpg new file mode 100644 index 000000000..dd4e0867d Binary files /dev/null and b/posts/review-2022/20230221153216_hu4944469646759786940.jpg differ diff --git a/posts/review-2022/index.html b/posts/review-2022/index.html new file mode 100644 index 000000000..070f883e8 --- /dev/null +++ b/posts/review-2022/index.html @@ -0,0 +1,15 @@ +2022年终总结 | 流动 +

      2022年终总结

      2022年已经过去1周多了,记录一下我的2022年。

      疫情

      2022年,是新冠疫情的第三年,也是切身感受到的一年。

      3月22日晚,8点半和同事刚上13号线地铁。

      紧接着看到群里有人说,公司大厦因为疫情封控了,只进不出。

      大厦封控

      第一次感受弹窗3,居家隔离。

      弹窗3

      居家隔离

      居家观察

      5月21日,开启居家办公。

      6月5日,开始到岗工作。

      11月17日,公司通知第二天居家办公。

      11月21日,小区通知临时管控。

      小区封控

      12月10日,媳妇中招。

      12月12日,自己中招。

      12月18日,娃中招。

      12月20日,开始到岗工作,持续了近一个月的居家隔离生活终于结束。

      老妈冒着北京疫情高峰感染的风险,过来帮我们带娃。

      从1周一检,到3天一检,不知道健康宝有没有年终报告,告诉你今年做了多少次核酸,相信会是很棒的一个数字。

      入学

      上半年赶着疫情的间隙,整理好了娃的入学资料。

      经过一个月的焦虑等待后,最终被附近的学校录取,也确实感受到了离家近的好处。

      详细经过见记录2022年海淀幼升小

      休假

      春节没有回家过年,上半年北京和老家交替出现疫情,最终在8月份休假回了趟家。

      驾照

      因为疫情,感觉还是得考个驾照,拖延了N年的事项提上了日程。

      6月份报名,一直拖到10月份才过的科目一。

      11月底开始摸车了,刚上2节课因为疫情封校了,现在学的都快忘光了。

      搬家

      年底公司通知搬家,又搬回了银科,兜兜转转,又回到了起点。

      又回银科

      博客

      2022年,把博客又捡了回来,今年多更新吧。

      或许是上了年纪,2022年发现泪点变得很低,健康、家人才是最重要的。

      新的一年,继续开源节流,做好防护,保护好家人。

      最后,祝大家新年快乐!

      💬评论
      \ No newline at end of file diff --git "a/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247.jpg" "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247.jpg" new file mode 100644 index 000000000..c1afe3df6 Binary files /dev/null and "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247.jpg" differ diff --git "a/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu17119673248843362961.jpg" "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu17119673248843362961.jpg" new file mode 100644 index 000000000..88e8318dd Binary files /dev/null and "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu17119673248843362961.jpg" differ diff --git "a/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu8765934698545978203.webp" "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu8765934698545978203.webp" new file mode 100644 index 000000000..7bda65ccc Binary files /dev/null and "b/posts/review-2022/\345\244\247\345\216\246\345\260\201\346\216\247_hu8765934698545978203.webp" differ diff --git "a/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247.jpg" "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247.jpg" new file mode 100644 index 000000000..e8a5f859f Binary files /dev/null and "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247.jpg" differ diff --git "a/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu3236688702478916991.jpg" "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu3236688702478916991.jpg" new file mode 100644 index 000000000..1573b0e25 Binary files /dev/null and "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu3236688702478916991.jpg" differ diff --git "a/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu7494342391696234418.webp" "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu7494342391696234418.webp" new file mode 100644 index 000000000..7bed40d72 Binary files /dev/null and "b/posts/review-2022/\345\260\217\345\214\272\345\260\201\346\216\247_hu7494342391696234418.webp" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237.jpg" "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237.jpg" new file mode 100644 index 000000000..02d53435f Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237.jpg" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu12702577351484703533.jpg" "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu12702577351484703533.jpg" new file mode 100644 index 000000000..2dbfe633d Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu12702577351484703533.jpg" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu8029850890261094517.webp" "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu8029850890261094517.webp" new file mode 100644 index 000000000..b1d819916 Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\350\247\202\345\257\237_hu8029850890261094517.webp" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273.jpg" "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273.jpg" new file mode 100644 index 000000000..299e56c63 Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273.jpg" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu17241506045196841522.webp" "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu17241506045196841522.webp" new file mode 100644 index 000000000..0dba92aac Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu17241506045196841522.webp" differ diff --git "a/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu9134605477740293062.jpg" "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu9134605477740293062.jpg" new file mode 100644 index 000000000..a130e381a Binary files /dev/null and "b/posts/review-2022/\345\261\205\345\256\266\351\232\224\347\246\273_hu9134605477740293062.jpg" differ diff --git "a/posts/review-2022/\345\274\271\347\252\2273.jpg" "b/posts/review-2022/\345\274\271\347\252\2273.jpg" new file mode 100644 index 000000000..c0bf82ddc Binary files /dev/null and "b/posts/review-2022/\345\274\271\347\252\2273.jpg" differ diff --git "a/posts/review-2022/\345\274\271\347\252\2273_hu4970225532395928247.jpg" "b/posts/review-2022/\345\274\271\347\252\2273_hu4970225532395928247.jpg" new file mode 100644 index 000000000..776eb85a6 Binary files /dev/null and "b/posts/review-2022/\345\274\271\347\252\2273_hu4970225532395928247.jpg" differ diff --git "a/posts/review-2022/\345\274\271\347\252\2273_hu9177248844689947037.webp" "b/posts/review-2022/\345\274\271\347\252\2273_hu9177248844689947037.webp" new file mode 100644 index 000000000..5458beba5 Binary files /dev/null and "b/posts/review-2022/\345\274\271\347\252\2273_hu9177248844689947037.webp" differ diff --git "a/posts/review-2022/\346\212\227\345\216\237\347\273\223\346\236\234.jpg" "b/posts/review-2022/\346\212\227\345\216\237\347\273\223\346\236\234.jpg" new file mode 100644 index 000000000..02f3a0dff Binary files /dev/null and "b/posts/review-2022/\346\212\227\345\216\237\347\273\223\346\236\234.jpg" differ diff --git a/posts/review-2023/20240104184720.png b/posts/review-2023/20240104184720.png new file mode 100644 index 000000000..8a7f3a0b3 Binary files /dev/null and b/posts/review-2023/20240104184720.png differ diff --git a/posts/review-2023/20240104184720_hu6551399417996237227.png b/posts/review-2023/20240104184720_hu6551399417996237227.png new file mode 100644 index 000000000..cf6e1cb96 Binary files /dev/null and b/posts/review-2023/20240104184720_hu6551399417996237227.png differ diff --git a/posts/review-2023/20240104184720_hu9917978577706645891.webp b/posts/review-2023/20240104184720_hu9917978577706645891.webp new file mode 100644 index 000000000..4761cc810 Binary files /dev/null and b/posts/review-2023/20240104184720_hu9917978577706645891.webp differ diff --git a/posts/review-2023/20240104190037.jpg b/posts/review-2023/20240104190037.jpg new file mode 100644 index 000000000..cc11e8b1e Binary files /dev/null and b/posts/review-2023/20240104190037.jpg differ diff --git a/posts/review-2023/20240104190037_hu12284403497067512678.webp b/posts/review-2023/20240104190037_hu12284403497067512678.webp new file mode 100644 index 000000000..0770eac89 Binary files /dev/null and b/posts/review-2023/20240104190037_hu12284403497067512678.webp differ diff --git a/posts/review-2023/20240104190037_hu711299581871800474.jpg b/posts/review-2023/20240104190037_hu711299581871800474.jpg new file mode 100644 index 000000000..6a1fbc68f Binary files /dev/null and b/posts/review-2023/20240104190037_hu711299581871800474.jpg differ diff --git a/posts/review-2023/20240104190649.jpg b/posts/review-2023/20240104190649.jpg new file mode 100644 index 000000000..31cc31d68 Binary files /dev/null and b/posts/review-2023/20240104190649.jpg differ diff --git a/posts/review-2023/20240104190649_hu15367748783548984010.jpg b/posts/review-2023/20240104190649_hu15367748783548984010.jpg new file mode 100644 index 000000000..86dfec8ad Binary files /dev/null and b/posts/review-2023/20240104190649_hu15367748783548984010.jpg differ diff --git a/posts/review-2023/20240104190649_hu6995472081980817639.webp b/posts/review-2023/20240104190649_hu6995472081980817639.webp new file mode 100644 index 000000000..905a9a512 Binary files /dev/null and b/posts/review-2023/20240104190649_hu6995472081980817639.webp differ diff --git a/posts/review-2023/20240104191508.jpg b/posts/review-2023/20240104191508.jpg new file mode 100644 index 000000000..7a0b53368 Binary files /dev/null and b/posts/review-2023/20240104191508.jpg differ diff --git a/posts/review-2023/20240104191508_hu15087142587093087512.webp b/posts/review-2023/20240104191508_hu15087142587093087512.webp new file mode 100644 index 000000000..cc15b26af Binary files /dev/null and b/posts/review-2023/20240104191508_hu15087142587093087512.webp differ diff --git a/posts/review-2023/20240104191508_hu8976748153054708540.jpg b/posts/review-2023/20240104191508_hu8976748153054708540.jpg new file mode 100644 index 000000000..018aeb8b9 Binary files /dev/null and b/posts/review-2023/20240104191508_hu8976748153054708540.jpg differ diff --git a/posts/review-2023/20240105132858.jpg b/posts/review-2023/20240105132858.jpg new file mode 100644 index 000000000..56952e58f Binary files /dev/null and b/posts/review-2023/20240105132858.jpg differ diff --git a/posts/review-2023/20240105132858_hu16448111177314676496.jpg b/posts/review-2023/20240105132858_hu16448111177314676496.jpg new file mode 100644 index 000000000..e3f332605 Binary files /dev/null and b/posts/review-2023/20240105132858_hu16448111177314676496.jpg differ diff --git a/posts/review-2023/20240105132858_hu8507553718241607449.webp b/posts/review-2023/20240105132858_hu8507553718241607449.webp new file mode 100644 index 000000000..799c7662d Binary files /dev/null and b/posts/review-2023/20240105132858_hu8507553718241607449.webp differ diff --git a/posts/review-2023/20240105132906.jpg b/posts/review-2023/20240105132906.jpg new file mode 100644 index 000000000..b7f2451e2 Binary files /dev/null and b/posts/review-2023/20240105132906.jpg differ diff --git a/posts/review-2023/20240105132906_hu11585877997970186359.jpg b/posts/review-2023/20240105132906_hu11585877997970186359.jpg new file mode 100644 index 000000000..1509f11a3 Binary files /dev/null and b/posts/review-2023/20240105132906_hu11585877997970186359.jpg differ diff --git a/posts/review-2023/20240105132906_hu2599040368997226671.webp b/posts/review-2023/20240105132906_hu2599040368997226671.webp new file mode 100644 index 000000000..d65a72585 Binary files /dev/null and b/posts/review-2023/20240105132906_hu2599040368997226671.webp differ diff --git a/posts/review-2023/20240105133739.jpg b/posts/review-2023/20240105133739.jpg new file mode 100644 index 000000000..097cf5854 Binary files /dev/null and b/posts/review-2023/20240105133739.jpg differ diff --git a/posts/review-2023/20240105133739_hu12607025353230258707.webp b/posts/review-2023/20240105133739_hu12607025353230258707.webp new file mode 100644 index 000000000..1adfe8b36 Binary files /dev/null and b/posts/review-2023/20240105133739_hu12607025353230258707.webp differ diff --git a/posts/review-2023/20240105133739_hu5679039284297789299.jpg b/posts/review-2023/20240105133739_hu5679039284297789299.jpg new file mode 100644 index 000000000..afa2a531e Binary files /dev/null and b/posts/review-2023/20240105133739_hu5679039284297789299.jpg differ diff --git a/posts/review-2023/20240105133747.jpg b/posts/review-2023/20240105133747.jpg new file mode 100644 index 000000000..7546643ed Binary files /dev/null and b/posts/review-2023/20240105133747.jpg differ diff --git a/posts/review-2023/20240105133747_hu17012946487418440240.webp b/posts/review-2023/20240105133747_hu17012946487418440240.webp new file mode 100644 index 000000000..038bd1286 Binary files /dev/null and b/posts/review-2023/20240105133747_hu17012946487418440240.webp differ diff --git a/posts/review-2023/20240105133747_hu6297396821765535546.jpg b/posts/review-2023/20240105133747_hu6297396821765535546.jpg new file mode 100644 index 000000000..d554f5b7c Binary files /dev/null and b/posts/review-2023/20240105133747_hu6297396821765535546.jpg differ diff --git a/posts/review-2023/20240105133752.jpg b/posts/review-2023/20240105133752.jpg new file mode 100644 index 000000000..f52486f14 Binary files /dev/null and b/posts/review-2023/20240105133752.jpg differ diff --git a/posts/review-2023/20240105133752_hu12313603963692183601.webp b/posts/review-2023/20240105133752_hu12313603963692183601.webp new file mode 100644 index 000000000..fb5ba292e Binary files /dev/null and b/posts/review-2023/20240105133752_hu12313603963692183601.webp differ diff --git a/posts/review-2023/20240105133752_hu13682909496706761490.jpg b/posts/review-2023/20240105133752_hu13682909496706761490.jpg new file mode 100644 index 000000000..80e77bdf5 Binary files /dev/null and b/posts/review-2023/20240105133752_hu13682909496706761490.jpg differ diff --git a/posts/review-2023/20240105133758.jpg b/posts/review-2023/20240105133758.jpg new file mode 100644 index 000000000..1149d00f5 Binary files /dev/null and b/posts/review-2023/20240105133758.jpg differ diff --git a/posts/review-2023/20240105133758_hu11337496756744297927.webp b/posts/review-2023/20240105133758_hu11337496756744297927.webp new file mode 100644 index 000000000..f68157bf6 Binary files /dev/null and b/posts/review-2023/20240105133758_hu11337496756744297927.webp differ diff --git a/posts/review-2023/20240105133758_hu17465420055628348745.jpg b/posts/review-2023/20240105133758_hu17465420055628348745.jpg new file mode 100644 index 000000000..775d1c838 Binary files /dev/null and b/posts/review-2023/20240105133758_hu17465420055628348745.jpg differ diff --git a/posts/review-2023/20240105133803.jpg b/posts/review-2023/20240105133803.jpg new file mode 100644 index 000000000..0ab212c41 Binary files /dev/null and b/posts/review-2023/20240105133803.jpg differ diff --git a/posts/review-2023/20240105133803_hu14918917217174035220.webp b/posts/review-2023/20240105133803_hu14918917217174035220.webp new file mode 100644 index 000000000..2c04b0d33 Binary files /dev/null and b/posts/review-2023/20240105133803_hu14918917217174035220.webp differ diff --git a/posts/review-2023/20240105133803_hu18115659086331184125.jpg b/posts/review-2023/20240105133803_hu18115659086331184125.jpg new file mode 100644 index 000000000..6768d418e Binary files /dev/null and b/posts/review-2023/20240105133803_hu18115659086331184125.jpg differ diff --git a/posts/review-2023/20240105133808.jpg b/posts/review-2023/20240105133808.jpg new file mode 100644 index 000000000..83dbbc8b7 Binary files /dev/null and b/posts/review-2023/20240105133808.jpg differ diff --git a/posts/review-2023/20240105133808_hu1518807698234557636.jpg b/posts/review-2023/20240105133808_hu1518807698234557636.jpg new file mode 100644 index 000000000..03327c79d Binary files /dev/null and b/posts/review-2023/20240105133808_hu1518807698234557636.jpg differ diff --git a/posts/review-2023/20240105133808_hu8118592834658162822.webp b/posts/review-2023/20240105133808_hu8118592834658162822.webp new file mode 100644 index 000000000..01c69fa7a Binary files /dev/null and b/posts/review-2023/20240105133808_hu8118592834658162822.webp differ diff --git a/posts/review-2023/20240105133814.jpg b/posts/review-2023/20240105133814.jpg new file mode 100644 index 000000000..c21a181ee Binary files /dev/null and b/posts/review-2023/20240105133814.jpg differ diff --git a/posts/review-2023/20240105133814_hu2907940507684878440.jpg b/posts/review-2023/20240105133814_hu2907940507684878440.jpg new file mode 100644 index 000000000..11e95b290 Binary files /dev/null and b/posts/review-2023/20240105133814_hu2907940507684878440.jpg differ diff --git a/posts/review-2023/20240105133814_hu7246078058378856150.webp b/posts/review-2023/20240105133814_hu7246078058378856150.webp new file mode 100644 index 000000000..8093927bb Binary files /dev/null and b/posts/review-2023/20240105133814_hu7246078058378856150.webp differ diff --git a/posts/review-2023/20240105133819.jpg b/posts/review-2023/20240105133819.jpg new file mode 100644 index 000000000..d7e586300 Binary files /dev/null and b/posts/review-2023/20240105133819.jpg differ diff --git a/posts/review-2023/20240105133819_hu18219205550022003430.jpg b/posts/review-2023/20240105133819_hu18219205550022003430.jpg new file mode 100644 index 000000000..965c32c0a Binary files /dev/null and b/posts/review-2023/20240105133819_hu18219205550022003430.jpg differ diff --git a/posts/review-2023/20240105133819_hu2756879506712662381.webp b/posts/review-2023/20240105133819_hu2756879506712662381.webp new file mode 100644 index 000000000..d6d56eafa Binary files /dev/null and b/posts/review-2023/20240105133819_hu2756879506712662381.webp differ diff --git a/posts/review-2023/20240105133825.jpg b/posts/review-2023/20240105133825.jpg new file mode 100644 index 000000000..b7bf7484c Binary files /dev/null and b/posts/review-2023/20240105133825.jpg differ diff --git a/posts/review-2023/20240105133825_hu13122219817455820098.jpg b/posts/review-2023/20240105133825_hu13122219817455820098.jpg new file mode 100644 index 000000000..f1c614ccf Binary files /dev/null and b/posts/review-2023/20240105133825_hu13122219817455820098.jpg differ diff --git a/posts/review-2023/20240105133825_hu4218178355415660863.webp b/posts/review-2023/20240105133825_hu4218178355415660863.webp new file mode 100644 index 000000000..afa1591c9 Binary files /dev/null and b/posts/review-2023/20240105133825_hu4218178355415660863.webp differ diff --git a/posts/review-2023/20240106165345.jpg b/posts/review-2023/20240106165345.jpg new file mode 100644 index 000000000..a64cd0229 Binary files /dev/null and b/posts/review-2023/20240106165345.jpg differ diff --git a/posts/review-2023/20240106165345_hu10579543192642342465.jpg b/posts/review-2023/20240106165345_hu10579543192642342465.jpg new file mode 100644 index 000000000..7af6c3cce Binary files /dev/null and b/posts/review-2023/20240106165345_hu10579543192642342465.jpg differ diff --git a/posts/review-2023/20240106165345_hu16873662734277985674.webp b/posts/review-2023/20240106165345_hu16873662734277985674.webp new file mode 100644 index 000000000..062978e40 Binary files /dev/null and b/posts/review-2023/20240106165345_hu16873662734277985674.webp differ diff --git a/posts/review-2023/index.html b/posts/review-2023/index.html new file mode 100644 index 000000000..2b340340d --- /dev/null +++ b/posts/review-2023/index.html @@ -0,0 +1,40 @@ +2023年终总结 | 流动 +

      2023年终总结

      2023年过完了,是时候来个总结了。

      博客

      2023年一共更新了15篇内容,共计12000字。

      Google Analytics全年统计

      访问Top3的文章:

      将博客部署到星际文件系统(IPFS)

      利用Github Actions定时抓取微博

      优化博客的累计布局偏移(CLS)问题

      主要是因为有在v2ex发帖导流,所以访问量高一些。

      2023年12月北京暴雪记录

      没想到的是一篇暴雪记录,收获了最多的评论,可能大家更容易共情。

      不过从侧面也说明了技术的东西并没有太多人看,所以后来就不再分享导流了。

      工作

      今年搬到了后厂村,见识了互联网的人流。

      在23年最后一个工作日,下班路上,算了一下,这一年晚上9点半之后打车59次。

      而且年底这段时间,打车愈发困难,至少要排队1个小时。

      相比之前一坐坐一天,每天中午会绕公司大楼转两圈。

      23年步行统计

      驾照

      驾照终于到手了

      趁着娃暑假、十一假期,开车上路了。

      第一次开车上路

      参与了第一次摇号。

      换了新手机

      用了5年的iPhone7 plus,过地铁NFC时不时刷不开了,感觉得换手机了。

      十一假期回来在官网订了15pro,需要11月21日才能发货。

      订货后,老手机的问题越来越多,换手机变的急迫起来。

      于是,开始刷实体店取货。

      用了探火app监控,10月11日中午抢到一台王府井取货。

      10月12日晚上8点出发,上16号线地铁后,老手机开始持续发烫,过一会自动黑屏了。

      按键有触感,但是屏幕不亮,怎么捣鼓也不行,手机越来越烫,都有点怕它炸了。

      换乘8号线的路上,试了试按住所有按键,手机重启了,看见苹果logo那一刻真好。

      重启完总算正常了,进店排队取货,提前了一个月拿到了新手机。

      王府井Apple Store +取完手机

      升级体验非常好,使用丝滑,再也不用插着充电器玩手机了,感谢老婆。

      大风 +公司大楼

      枫叶 +秋 +冬 +冬

      秋天的百望山 +冬天的百望山 +冬天的百望山

      财务

      提前还了两笔房贷,希望明年可以把商贷还完。

      股市收益率-1.11%,港股套牢中,美股稍微回了点血。

      股市收益

      本命年了,希望一切顺利。

      祝大家新年快乐!

      💬评论
      \ No newline at end of file diff --git a/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/index.html b/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/index.html new file mode 100644 index 000000000..a9cff25de --- /dev/null +++ b/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/index.html @@ -0,0 +1,77 @@ +解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 | 流动 +

      解决 "undeclared name: any (requires version go1.18 or later)" 编译错误

      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +$ 
      +$ protoc-gen-go --version
      +protoc-gen-go v1.34.2
      +$ 
      +$ sh make.sh
      +user.pb.go:123:45: undeclared name: any (requires version go1.18 or later)
      +$ 
      +

      流水线编译报错,其中make.sh文件代码:

      ...
      +
      +protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto 
      +
      +...
      +
      +go build
      +

      同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。

      登到流水线编译机器上,看了下go的版本已经是1.18.1了,按理不应该报这个错误的。

      关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用master分支编译了一下,也还是报这个错误。

      手动执行make.sh里的每条命令,发现是protoc编译pb文件时报的这个错误。

      经过一番查找后,发现是protoc-gen-go在4月份更新了版本,引入了新特性。

      protoc-gen-go’s versions

      Versions in this module
      +v1
      +    v1.34.2 Jun 11, 2024
      +    v1.34.1 May 6, 2024
      +    v1.34.0 Apr 30, 2024
      +    v1.33.0 Mar 5, 2024
      +    v1.32.0 Dec 22, 2023
      +

      Protobuf Editions Overview

      Protobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = “proto2” or syntax = “proto3” at the top of proto definition files, you use an edition number, such as edition = “2024”, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.

      Instead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.

      改用历史版本后解决。

      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
      +
      💬评论
      \ No newline at end of file diff --git a/posts/strings-and-numbers-in-mysql/index.html b/posts/strings-and-numbers-in-mysql/index.html new file mode 100644 index 000000000..47aaa4c58 --- /dev/null +++ b/posts/strings-and-numbers-in-mysql/index.html @@ -0,0 +1,112 @@ +mysql中字符串和整型自动转换的问题 | 流动 +

      mysql中字符串和整型自动转换的问题

      表结构如下

      desc info;
      ++-------+-----------------+------+-----+---------+----------------+
      +| Field | Type            | Null | Key | Default | Extra          |
      ++-------+-----------------+------+-----+---------+----------------+
      +| id    | int(8) unsigned | NO   | PRI | NULL    | auto_increment |
      +| name  | varchar(20)     | YES  |     | NULL    |                |
      ++-------+-----------------+------+-----+---------+----------------+
      +2 rows in set (0.00 sec)
      +

      执行sql.

      insert into info values ('', 'xxx');
      +insert into info values ('', 'yyy');
      +

      查询记录.

      select * from info;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      +|  2 | yyy  |
      ++----+------+
      +2 rows in set (0.00 sec)
      +

      执行下面sql.

      select * from info where id = 1;
      +
      +select * from info where id = '1aaaa';
      +

      你先想想结果会是什么。

      select * from info where id = 1;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set (0.00 sec)
      +
      +select * from info where id = '1aaaa';
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set, 1 warning (0.01 sec)
      +

      两个sql都查到了id = 1的记录,唯一的区别在于第二个sql有一个warning错误。

      show warnings;
      ++---------+------+-------------------------------------------+
      +| Level   | Code | Message                                   |
      ++---------+------+-------------------------------------------+
      +| Warning | 1292 | Truncated incorrect DOUBLE value: '1aaaa' |
      ++---------+------+-------------------------------------------+
      +1 row in set (0.00 sec)
      +

      mysql在查询时,会根据字段类型进行转换,这里1aaaa被转为了1

      💬评论
      \ No newline at end of file diff --git a/posts/swoft-anaotion-processor-analysis/index.html b/posts/swoft-anaotion-processor-analysis/index.html new file mode 100644 index 000000000..c58d945a3 --- /dev/null +++ b/posts/swoft-anaotion-processor-analysis/index.html @@ -0,0 +1,375 @@ +Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 | 流动 +

      Swoft 框架运行分析(二) —— AnnotationProcessor模块分析

      上一篇介绍了,SwoftApplication里定义了6个Processor对象。

      protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      所有的Processor实现都在framework\src\Processor目录下。

      1. EnvProcessor,运行环境检查。

      2. ConfigProcessor,配置相关。

      3. AnnotationProcessor,注解相关。

      4. BeanProcessor,Bean相关。

      5. EventProcessor,事件相关。

      6. ConsoleProcessor,命令行输入相关。

      今天先讲一下AnnotationProcessor这个模块的实现。

      <?php declare(strict_types=1);
      +
      +namespace Swoft\Processor;
      +
      +use Exception;
      +use Swoft\Annotation\AnnotationRegister;
      +use Swoft\Log\Helper\CLog;
      +
      +/**
      + * Annotation processor
      + * @since 2.0
      + */
      +class AnnotationProcessor extends Processor
      +{
      +    /**
      +     * Handle annotation
      +     *
      +     * @return bool
      +     * @throws Exception
      +     */
      +    public function handle(): bool
      +    {
      +        if (!$this->application->beforeAnnotation()) {
      +            CLog::warning('Stop annotation processor by beforeAnnotation return false');
      +            return false;
      +        }
      +
      +        $app = $this->application;
      +
      +        // Find AutoLoader classes. Parse and collect annotations.
      +        AnnotationRegister::load([
      +            'inPhar'               => \IN_PHAR,
      +            'basePath'             => $app->getBasePath(),
      +            'notifyHandler'        => [$this, 'notifyHandler'],
      +            'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
      +            'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
      +        ]);
      +
      +        $stats = AnnotationRegister::getClassStats();
      +
      +        CLog::info(
      +            'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
      +            $stats['autoloader'],
      +            $stats['annotation'],
      +            $stats['parser']
      +        );
      +
      +        return $this->application->afterAnnotation();
      +    }
      +
      +    /**
      +     * @param string $type
      +     * @param string $target
      +     * @see \Swoft\Annotation\Resource\AnnotationResource::load()
      +     */
      +    public function notifyHandler(string $type, $target): void
      +    {
      +        switch ($type) {
      +            case 'excludeNs':
      +                CLog::debug('Exclude namespace %s', $target);
      +                break;
      +            case 'noLoaderFile':
      +                CLog::debug('No autoloader on %s', $target);
      +                break;
      +            case 'noLoaderClass':
      +                CLog::debug('Autoloader class not exist %s', $target);
      +                break;
      +            case 'findLoaderClass':
      +                CLog::debug('Find autoloader %s', $target);
      +                break;
      +            case 'addLoaderClass':
      +                CLog::debug('Parse autoloader %s', $target);
      +                break;
      +            case 'noExistClass':
      +                CLog::debug('Skip interface or trait %s', $target);
      +                break;
      +        }
      +    }
      +}
      +

      核心逻辑调用AnnotationRegister类的load方法,定义如下。

      /**
      +    * Load annotation class
      +    *
      +    * @param array $config
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function load(array $config = []): void
      +{
      +    $resource = new AnnotationResource($config);
      +    $resource->load();
      +}
      +

      这里又调用了AnnotationResource类的load方法,定义如下。

      /**
      +    * Load annotation resource by find ClassLoader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function load(): void
      +{
      +    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
      +
      +    foreach ($prefixDirsPsr4 as $ns => $paths) {
      +        // Only scan namespaces
      +        if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // It is excluded psr4 prefix
      +        if ($this->isExcludedPsr4Prefix($ns)) {
      +            AnnotationRegister::registerExcludeNs($ns);
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // Find package/component loader class
      +        foreach ($paths as $path) {
      +            $loaderFile = $this->getAnnotationClassLoaderFile($path);
      +            if (!file_exists($loaderFile)) {
      +                $this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
      +                continue;
      +            }
      +
      +            $loaderClass = $this->getAnnotationLoaderClassName($ns);
      +            if (!class_exists($loaderClass)) {
      +                $this->notify('noLoaderClass', $loaderClass);
      +                continue;
      +            }
      +
      +            $loaderObject = new $loaderClass();
      +            if (!$loaderObject instanceof LoaderInterface) {
      +                $this->notify('invalidLoader', $loaderFile);
      +                continue;
      +            }
      +
      +            $this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
      +
      +            // If is disable, will skip scan annotation classes
      +            if (!isset($this->disabledAutoLoaders[$loaderClass])) {
      +                AnnotationRegister::registerAutoLoaderFile($loaderFile);
      +                $this->notify('addLoaderClass', $loaderClass);
      +                $this->loadAnnotation($loaderObject);
      +            }
      +
      +            // Storage auto loader to register
      +            AnnotationRegister::addAutoLoader($ns, $loaderObject);
      +        }
      +    }
      +}
      +

      通过getPrefixesPsr4方法获取所有自动加载的命名空间和目录,遍历目录下的AutoLoader.php文件。

      通过registerAutoLoaderFile注册自动加载文件到AnnotationRegister对象上。

      然后调用了loadAnnotation方法,传入的是一个autoload对象。

      /**
      +    * Load annotations from an component loader config.
      +    *
      +    * @param LoaderInterface $loader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function loadAnnotation(LoaderInterface $loader): void
      +{
      +    $nsPaths = $loader->getPrefixDirs();
      +
      +    foreach ($nsPaths as $ns => $path) {
      +        $iterator = DirectoryHelper::recursiveIterator($path);
      +
      +        /* @var SplFileInfo $splFileInfo */
      +        foreach ($iterator as $splFileInfo) {
      +            $filePath = $splFileInfo->getPathname();
      +            // $splFileInfo->isDir();
      +            if (is_dir($filePath)) {
      +                continue;
      +            }
      +
      +            $fileName  = $splFileInfo->getFilename();
      +            $extension = $splFileInfo->getExtension();
      +
      +            if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
      +                continue;
      +            }
      +
      +            // It is exclude filename
      +            if (isset($this->excludedFilenames[$fileName])) {
      +                AnnotationRegister::registerExcludeFilename($fileName);
      +                continue;
      +            }
      +
      +            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
      +            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
      +            $className = sprintf('%s%s', $ns, $pathName);
      +
      +            // Fix repeat included file bug
      +            $autoload = in_array($filePath, $this->includedFiles, true);
      +
      +            // Will filtering: interfaces and traits
      +            if (!class_exists($className, !$autoload)) {
      +                $this->notify('noExistClass', $className);
      +                continue;
      +            }
      +
      +            // Parse annotation
      +            $this->parseAnnotation($ns, $className);
      +        }
      +    }
      +}
      +

      通过getPrefixDirs获取当前命名空间的目录,然后通过recursiveIterator遍历目录下的文件。

      排除目录和非.php结尾的文件,最后会调用parseAnnotation方法。

      /**
      +    * Parser annotation
      +    *
      +    * @param string $namespace
      +    * @param string $className
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseAnnotation(string $namespace, string $className): void
      +{
      +    // Annotation reader
      +    $reflectionClass = new ReflectionClass($className);
      +
      +    // Fix ignore abstract
      +    if ($reflectionClass->isAbstract()) {
      +        return;
      +    }
      +    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
      +
      +    if (!empty($oneClassAnnotation)) {
      +        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
      +    }
      +}
      +

      这里调用了parseOneClassAnnotation方法。

      /**
      +    * Parse an class annotation
      +    *
      +    * @param ReflectionClass $reflectionClass
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
      +{
      +    // Annotation reader
      +    $reader    = new AnnotationReader();
      +    $className = $reflectionClass->getName();
      +
      +    $oneClassAnnotation = [];
      +    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);
      +
      +    // Register annotation parser
      +    foreach ($classAnnotations as $classAnnotation) {
      +        if ($classAnnotation instanceof AnnotationParser) {
      +            $this->registerParser($className, $classAnnotation);
      +
      +            return [];
      +        }
      +    }
      +
      +    // Class annotation
      +    if (!empty($classAnnotations)) {
      +        $oneClassAnnotation['annotation'] = $classAnnotations;
      +        $oneClassAnnotation['reflection'] = $reflectionClass;
      +    }
      +
      +    // Property annotation
      +    $reflectionProperties = $reflectionClass->getProperties();
      +    foreach ($reflectionProperties as $reflectionProperty) {
      +        $propertyName        = $reflectionProperty->getName();
      +        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);
      +
      +        if (!empty($propertyAnnotations)) {
      +            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
      +            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
      +        }
      +    }
      +
      +    // Method annotation
      +    $reflectionMethods = $reflectionClass->getMethods();
      +    foreach ($reflectionMethods as $reflectionMethod) {
      +        $methodName        = $reflectionMethod->getName();
      +        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);
      +
      +        if (!empty($methodAnnotations)) {
      +            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
      +            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
      +        }
      +    }
      +
      +    $parentReflectionClass = $reflectionClass->getParentClass();
      +    if ($parentReflectionClass !== false) {
      +        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
      +        if (!empty($parentClassAnnotation)) {
      +            $oneClassAnnotation['parent'] = $parentClassAnnotation;
      +        }
      +    }
      +
      +    return $oneClassAnnotation;
      +}
      +

      这里就是解析注解了,可以看到分别有类注解、属性注解和方法注解三类。

      这里注意这一段代码。

      // Register annotation parser
      +foreach ($classAnnotations as $classAnnotation) {
      +    if ($classAnnotation instanceof AnnotationParser) {
      +        $this->registerParser($className, $classAnnotation);
      +
      +        return [];
      +    }
      +}
      +

      遍历注解类,如果注解属于AnnotationParser实例,这里调用registerParser进行注册。

      /**
      +    * @param string $annotationClass
      +    * @param string $parserClassName
      +    */
      +public static function registerParser(string $annotationClass, string $parserClassName): void
      +{
      +    self::$classStats['parser']++;
      +    self::$parsers[$annotationClass] = $parserClassName;
      +}
      +

      回到上一个方法,解析完后,又调用了AnnotationRegister类的registerAnnotation方法进行注册。

      /**
      +    * @param string $loadNamespace
      +    * @param string $className
      +    * @param array  $classAnnotation
      +    */
      +public static function registerAnnotation(string $loadNamespace, string $className, array $classAnnotation): void
      +{
      +    self::$classStats['annotation']++;
      +    self::$annotations[$loadNamespace][$className] = $classAnnotation;
      +}
      +

      至此,整个AnnotationProcessor加载完毕,这里AnnotationRegister类里会有annotationsparsers两个属性,这个信息在后面的BeanProcessor里还会用到。

      💬评论
      \ No newline at end of file diff --git a/posts/swoft-bean-processor-analysis/index.html b/posts/swoft-bean-processor-analysis/index.html new file mode 100644 index 000000000..c6893faa8 --- /dev/null +++ b/posts/swoft-bean-processor-analysis/index.html @@ -0,0 +1,1097 @@ +Swoft 框架运行分析(三) —— BeanProcessor模块分析 | 流动 +

      Swoft 框架运行分析(三) —— BeanProcessor模块分析

      今天讲一下BeanProcessor模块,先看一下handle方法实现。

      /**
      +    * Handle bean
      +    *
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws AnnotationException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeBean()) {
      +        return false;
      +    }
      +
      +    $handler     = new BeanHandler();
      +    $definitions = $this->getDefinitions();
      +    $parsers     = AnnotationRegister::getParsers();
      +    $annotations = AnnotationRegister::getAnnotations();
      +
      +    BeanFactory::addDefinitions($definitions);
      +    BeanFactory::addAnnotations($annotations);
      +    BeanFactory::addParsers($parsers);
      +    BeanFactory::setHandler($handler);
      +    BeanFactory::init();
      +
      +    /* @var Config $config*/
      +    $config = BeanFactory::getBean('config');
      +
      +    CLog::info('config path=%s', $config->getPath());
      +    CLog::info('config env=%s', $config->getEnv());
      +
      +    $stats = BeanFactory::getStats();
      +
      +    CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats));
      +
      +    return $this->application->afterBean();
      +}
      +

      先通过getDefinitions方法获取所有的Bean定义。

      /**
      +    * Get bean definitions
      +    *
      +    * @return array
      +    */
      +private function getDefinitions(): array
      +{
      +    // Core beans
      +    $definitions = [];
      +    $autoLoaders = AnnotationRegister::getAutoLoaders();
      +
      +    // get disabled loaders by application
      +    $disabledLoaders = $this->application->getDisabledAutoLoaders();
      +
      +    foreach ($autoLoaders as $autoLoader) {
      +        if (!$autoLoader instanceof DefinitionInterface) {
      +            continue;
      +        }
      +
      +        $loaderClass = get_class($autoLoader);
      +
      +        // If the component is disabled by user.
      +        if (isset($disabledLoaders[$loaderClass])) {
      +            CLog::info('Auto loader(%s) is <cyan>disabled</cyan>, skip handle it', $loaderClass);
      +            continue;
      +        }
      +
      +        // If the component is not enabled.
      +        if ($autoLoader instanceof ComponentInterface && !$autoLoader->isEnable()) {
      +            continue;
      +        }
      +
      +        $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());
      +    }
      +
      +    // Bean definitions
      +    $beanFile = $this->application->getBeanFile();
      +    $beanFile = alias($beanFile);
      +
      +    if (!file_exists($beanFile)) {
      +        throw new InvalidArgumentException(
      +            sprintf('The bean config file of %s is not exist!', $beanFile)
      +        );
      +    }
      +
      +    $beanDefinitions = require $beanFile;
      +    $definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +
      +    return $definitions;
      +}
      +

      通过AnnotationRegister::getAutoLoaders()拿到所有的autoloader对象,排除掉非DefinitionInterface对象,通过bean()方法获取定义的Bean信息。

      这里以http-server\src\AutoLoader.php为例。

      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server;
      +
      +use function bean;
      +use function dirname;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Helper\ComposerJSON;
      +use Swoft\Http\Message\ContentType;
      +use Swoft\Http\Message\Response;
      +use Swoft\Http\Server\Formatter\HtmlResponseFormatter;
      +use Swoft\Http\Server\Formatter\JsonResponseFormatter;
      +use Swoft\Http\Server\Formatter\XmlResponseFormatter;
      +use Swoft\Http\Server\Parser\JsonRequestParser;
      +use Swoft\Http\Server\Parser\XmlRequestParser;
      +use Swoft\Http\Server\Swoole\RequestListener;
      +use Swoft\Server\SwooleEvent;
      +use Swoft\SwoftComponent;
      +
      +/**
      + * Class AutoLoader
      + *
      + * @since 2.0
      + */
      +class AutoLoader extends SwoftComponent
      +{
      +    /**
      +     * Metadata information for the component.
      +     *
      +     * @return array
      +     * @see ComponentInterface::getMetadata()
      +     */
      +    public function metadata(): array
      +    {
      +        $jsonFile = dirname(__DIR__) . '/composer.json';
      +
      +        return ComposerJSON::open($jsonFile)->getMetadata();
      +    }
      +
      +    /**
      +     * Get namespace and dirs
      +     *
      +     * @return array
      +     */
      +    public function getPrefixDirs(): array
      +    {
      +        return [
      +            __NAMESPACE__ => __DIR__,
      +        ];
      +    }
      +
      +    /**
      +     * @return array
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function beans(): array
      +    {
      +        return [
      +            'httpRequest'     => [
      +                'parsers' => [
      +                    ContentType::XML  => bean(XmlRequestParser::class),
      +                    ContentType::JSON => bean(JsonRequestParser::class),
      +                ]
      +            ],
      +            'httpResponse'    => [
      +                'format'     => Response::FORMAT_JSON,
      +                'formatters' => [
      +                    Response::FORMAT_HTML => bean(HtmlResponseFormatter::class),
      +                    Response::FORMAT_JSON => bean(JsonResponseFormatter::class),
      +                    Response::FORMAT_XML  => bean(XmlResponseFormatter::class),
      +                ]
      +            ],
      +            'acceptFormatter' => [
      +                'formats' => [
      +                    ContentType::JSON => Response::FORMAT_JSON,
      +                    ContentType::HTML => Response::FORMAT_HTML,
      +                    ContentType::XML  => Response::FORMAT_XML,
      +                ]
      +            ],
      +            'httpServer'      => [
      +                'on' => [
      +                    SwooleEvent::REQUEST => bean(RequestListener::class)
      +                ]
      +            ],
      +            'httpRouter'      => [
      +                'name'            => 'swoft-http-router',
      +                // config
      +                'ignoreLastSlash' => true,
      +                'tmpCacheNumber'  => 500,
      +            ],
      +        ];
      +    }
      +}
      +

      可以看到,这里通过beans()定义了httpRequesthttpResponseacceptFormatterhttpServerhttpRouter四个Bean对象。

      回到上面getDefinitions方法。

      $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());

      然后将Bean信息添加到definitions对象上。

      之后通过$beanFile = $this->application->getBeanFile();获取bean配置文件。

      $beanDefinitions = require $beanFile;
      +$definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +

      加载配置文件,然后将Bean信息添加到definitions对象上。

      可以看到Bean有两种定义方式:通过AutoLoader和配置文件,与swoft官方文档里的说明一致。

      回到handle方法。

      $parsers     = AnnotationRegister::getParsers();
      +$annotations = AnnotationRegister::getAnnotations();
      +

      还记得上一篇文章最后提到的AnnotationRegister类的annotationsparsers两个属性吗?这里通过getParsersgetAnnotations获取这两个属性。

      BeanFactory::addDefinitions($definitions);
      +BeanFactory::addAnnotations($annotations);
      +BeanFactory::addParsers($parsers);
      +BeanFactory::setHandler($handler);
      +BeanFactory::init();
      +

      向BeanFatory注册信息。

      /**
      +    * Init
      +    *
      +    * @return void
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function init(): void
      +{
      +    Container::getInstance()->init();
      +}
      +
      +...
      +
      +/**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public static function addDefinitions(array $definitions): void
      +{
      +    Container::getInstance()->addDefinitions($definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public static function addAnnotations(array $annotations): void
      +{
      +    Container::getInstance()->addAnnotations($annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public static function addParsers(array $annotationParsers): void
      +{
      +    Container::getInstance()->addParsers($annotationParsers);
      +}
      +
      +/**
      +    * Set bean handler
      +    *
      +    * @param HandlerInterface $handler
      +    */
      +public static function setHandler(HandlerInterface $handler): void
      +{
      +    Container::getInstance()->setHandler($handler);
      +}
      +

      这里可以看到所有的方法,最终都调用的是Swoft\Bean\Container类。

      /**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public function addDefinitions(array $definitions): void
      +{
      +    $this->definitions = ArrayHelper::merge($this->definitions, $definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public function addAnnotations(array $annotations): void
      +{
      +    $this->annotations = ArrayHelper::merge($this->annotations, $annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public function addParsers(array $annotationParsers): void
      +{
      +    $this->parsers = ArrayHelper::merge($this->parsers, $annotationParsers);
      +}
      +
      +
      +/**
      +    * @param HandlerInterface $handler
      +    */
      +public function setHandler(HandlerInterface $handler): void
      +{
      +    $this->handler = $handler;
      +}
      +

      这四个方法就是注册属性,接下来是重头戏init方法。

      /**
      +    * Init
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function init(): void
      +{
      +    // Parse annotations
      +    $this->parseAnnotations();
      +
      +    // Parse definitions
      +    $this->parseDefinitions();
      +
      +    // Init beans
      +    $this->initializeBeans();
      +}
      +

      先看parseAnnotations方法,从代码注释上也可以看出大概,解析注解,接下来我们看下具体是如何实现的。

      /**
      +    * Parse annotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseAnnotations(): void
      +{
      +    $annotationParser = new AnnotationObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +    $annotationData   = $annotationParser->parseAnnotations($this->annotations, $this->parsers);
      +
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
      +}
      +

      声明了一个AnnotationObjParser对象,调用了parseAnnotations方法。

      /**
      +    * Parse annotations
      +    *
      +    * @param array $annotations
      +    * @param array $parsers
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    */
      +public function parseAnnotations(array $annotations, array $parsers): array
      +{
      +    $this->parsers     = $parsers;
      +    $this->annotations = $annotations;
      +
      +    foreach ($this->annotations as $loadNameSpace => $classes) {
      +        foreach ($classes as $className => $classOneAnnotations) {
      +            $this->parseOneClassAnnotations($className, $classOneAnnotations);
      +        }
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      这里遍历所有的annotation类,循环调用parseOneClassAnnotations进行解析。

      /**
      +    * Parse class all annotations
      +    *
      +    * @param string $className
      +    * @param array  $classOneAnnotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
      +{
      +    // Check class annotation tag
      +    if (!isset($classOneAnnotations['annotation'])) {
      +        throw new AnnotationException(
      +            sprintf('Property or method(%s) with `@xxx` must be define class annotation', $className)
      +        );
      +    }
      +
      +    // Parse class annotations
      +    $classAnnotations = $classOneAnnotations['annotation'];
      +    $reflectionClass  = $classOneAnnotations['reflection'];
      +
      +    $classAry = [
      +        $className,
      +        $reflectionClass,
      +        $classAnnotations
      +    ];
      +
      +    $objectDefinition = $this->parseClassAnnotations($classAry);
      +
      +    // Parse property annotations
      +    $propertyInjects        = [];
      +    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
      +    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
      +        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
      +        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
      +        if ($propertyInject) {
      +            $propertyInjects[$propertyName] = $propertyInject;
      +        }
      +    }
      +
      +    // Parse method annotations
      +    $methodInjects        = [];
      +    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
      +    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
      +        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];
      +
      +        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
      +        if ($methodInject) {
      +            $methodInjects[$methodName] = $methodInject;
      +        }
      +    }
      +
      +    if (!$objectDefinition) {
      +        return;
      +    }
      +
      +    if (!empty($propertyInjects)) {
      +        $objectDefinition->setPropertyInjections($propertyInjects);
      +    }
      +
      +    if (!empty($methodInjects)) {
      +        $objectDefinition->setMethodInjections($methodInjects);
      +    }
      +
      +    // Object definition and class name
      +    $name         = $objectDefinition->getName();
      +    $aliase       = $objectDefinition->getAlias();
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $name;
      +
      +    $this->classNames[$className]   = array_unique($classNames);
      +    $this->objectDefinitions[$name] = $objectDefinition;
      +
      +    if (!empty($aliase)) {
      +        $this->aliases[$aliase] = $name;
      +    }
      +}
      +

      这里可以看到分别有类注解、属性注解和方法注解三类。

      对应官方文档的注解说明

      /**
      +    * @param array $classAry
      +    *
      +    * @return ObjectDefinition|null
      +    */
      +private function parseClassAnnotations(array $classAry): ?ObjectDefinition
      +{
      +    [, , $classAnnotations] = $classAry;
      +
      +    $objectDefinition = null;
      +    foreach ($classAnnotations as $annotation) {
      +        $annotationClass = get_class($annotation);
      +        if (!isset($this->parsers[$annotationClass])) {
      +            continue;
      +        }
      +
      +        $parserClassName  = $this->parsers[$annotationClass];
      +        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
      +
      +        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
      +        if (empty($data)) {
      +            continue;
      +        }
      +
      +        if (count($data) !== 4) {
      +            throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
      +        }
      +
      +        [$name, $className, $scope, $alias] = $data;
      +        $name = empty($name) ? $className : $name;
      +
      +        if (empty($className)) {
      +            throw new InvalidArgumentException(sprintf('%s with class name can not be empty', $annotationClass));
      +        }
      +
      +        // Multiple coverage
      +        $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
      +    }
      +
      +    return $objectDefinition;
      +}
      +

      类注解,这里会调用对应解析类的parse方法。

      这里以websocket-server\src\Annotation\Mapping\WsModule.phpwebsocket-server\src\Annotation\Parser\WsModuleParser.php为例。

      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Mapping;
      +
      +use Doctrine\Common\Annotations\Annotation\Attribute;
      +use Doctrine\Common\Annotations\Annotation\Attributes;
      +use Doctrine\Common\Annotations\Annotation\Required;
      +use Doctrine\Common\Annotations\Annotation\Target;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +
      +/**
      + * Class WebSocket - mark an websocket module handler class
      + *
      + * @since 2.0
      + *
      + * @Annotation
      + * @Target("CLASS")
      + * @Attributes(
      + *     @Attribute("name", type="string"),
      + *     @Attribute("path", type="string"),
      + *     @Attribute("controllers", type="array"),
      + *     @Attribute("messageParser", type="string"),
      + * )
      + */
      +final class WsModule
      +{
      +    /**
      +     * Websocket route path.(it must unique in a application)
      +     *
      +     * @var string
      +     * @Required()
      +     */
      +    private $path = '/';
      +
      +    /**
      +     * Module name.
      +     *
      +     * @var string
      +     */
      +    private $name = '';
      +
      +    /**
      +     * Routing path params binding. eg. {"id"="\d+"}
      +     *
      +     * @var array
      +     */
      +    private $params = [];
      +
      +    /**
      +     * Message controllers of the module
      +     *
      +     * @var string[]
      +     */
      +    private $controllers = [];
      +
      +    /**
      +     * Message parser class for the module
      +     *
      +     * @var string
      +     */
      +    private $messageParser = RawTextParser::class;
      +
      +    /**
      +     * Default message command. Format 'controller.action'
      +     *
      +     * @var string
      +     */
      +    private $defaultCommand = 'home.index';
      +
      +    /**
      +     * Default message opcode for response. please see WEBSOCKET_OPCODE_*
      +     *
      +     * @var int
      +     */
      +    private $defaultOpcode = 0;
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $values
      +     */
      +    public function __construct(array $values)
      +    {
      +        if (isset($values['value'])) {
      +            $this->path = (string)$values['value'];
      +        } elseif (isset($values['path'])) {
      +            $this->path = (string)$values['path'];
      +        }
      +
      +        if (isset($values['name'])) {
      +            $this->name = (string)$values['name'];
      +        }
      +
      +        if (isset($values['params'])) {
      +            $this->params = (array)$values['params'];
      +        }
      +
      +        if (isset($values['controllers'])) {
      +            $this->controllers = (array)$values['controllers'];
      +        }
      +
      +        if (isset($values['messageParser'])) {
      +            $this->messageParser = $values['messageParser'];
      +        }
      +
      +        if (isset($values['defaultOpcode'])) {
      +            $this->defaultOpcode = (int)$values['defaultOpcode'];
      +        }
      +
      +        if (isset($values['defaultCommand'])) {
      +            $this->defaultCommand = $values['defaultCommand'];
      +        }
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getPath(): string
      +    {
      +        return $this->path;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getMessageParser(): string
      +    {
      +        return $this->messageParser;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getDefaultCommand(): string
      +    {
      +        return $this->defaultCommand;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getName(): string
      +    {
      +        return $this->name;
      +    }
      +
      +    /**
      +     * @return string[]
      +     */
      +    public function getControllers(): array
      +    {
      +        return $this->controllers;
      +    }
      +
      +    /**
      +     * @return array
      +     */
      +    public function getParams(): array
      +    {
      +        return $this->params;
      +    }
      +
      +    /**
      +     * @return int
      +     */
      +    public function getDefaultOpcode(): int
      +    {
      +        return $this->defaultOpcode;
      +    }
      +}
      +

      WsModule声明了一个类注解。

      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Stdlib\Helper\Str;
      +use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +use Swoft\WebSocket\Server\Router\RouteRegister;
      +
      +/**
      + * Class WebSocketParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(WsModule::class)
      + */
      +class WsModuleParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int      $type Class or Method or Property
      +     * @param WsModule $ann  Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $ann): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@WsModule` must be defined on class!');
      +        }
      +
      +        $class = $this->className;
      +
      +        RouteRegister::bindModule($class, [
      +            'path'           => $ann->getPath() ?: Str::getClassName($class, 'Module'),
      +            'name'           => $ann->getName(),
      +            'params'         => $ann->getParams(),
      +            'class'          => $class,
      +            'eventMethods'   => [],
      +            'controllers'    => $ann->getControllers(),
      +            'messageParser'  => $ann->getMessageParser() ?: RawTextParser::class,
      +            'defaultOpcode' => $ann->getDefaultOpcode(),
      +            'defaultCommand' => $ann->getDefaultCommand(),
      +        ]);
      +
      +        return [$class, $class, Bean::SINGLETON, ''];
      +    }
      +}
      +

      按上一篇文章说明,这里WsModuleParser会被标记为注解类WsModule的注解解析类。

      解析注解的时候,会调用WsModuleParserparse方法,这里通过RouteRegister::bindModule做了一些路由操作,这里后续再讲,这里不做深入介绍。

      属性和方法注解,也是类似的,parseAnnotations方法就讲完了。

      回到Container类的init方法,接下来调用了parseDefinitions方法。

      /**
      +    * Parse definitions
      +    */
      +private function parseDefinitions(): void
      +{
      +    $annotationParser = new DefinitionObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +
      +    // Collect info
      +    $definitionData = $annotationParser->parseDefinitions();
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $definitionData;
      +}
      +

      声明了一个DefinitionObjParser对象,调用了parseDefinitions方法。

      /**
      +    * Parse definitions
      +    *
      +    * @return array
      +    */
      +public function parseDefinitions(): array
      +{
      +    foreach ($this->definitions as $beanName => $definition) {
      +        if (isset($this->objectDefinitions[$beanName])) {
      +            $objectDefinition = $this->objectDefinitions[$beanName];
      +            $this->resetObjectDefinition($beanName, $objectDefinition, $definition);
      +            continue;
      +        }
      +
      +        $this->createObjectDefinition($beanName, $definition);
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      遍历所有的Bean对象,调用createObjectDefinition方法。

      /**
      +    * Create object definition for definition
      +    *
      +    * @param string $beanName
      +    * @param array  $definition
      +    */
      +private function createObjectDefinition(string $beanName, array $definition): void
      +{
      +    $className = $definition['class'] ?? '';
      +    if (empty($className)) {
      +        throw new InvalidArgumentException(sprintf('%s key for definition must be defined class', $beanName));
      +    }
      +
      +    $objDefinition = new ObjectDefinition($beanName, $className);
      +    $objDefinition = $this->updateObjectDefinitionByDefinition($objDefinition, $definition);
      +
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $beanName;
      +
      +    $this->classNames[$className]       = array_unique($classNames);
      +    $this->objectDefinitions[$beanName] = $objDefinition;
      +}
      +

      声明了ObjectDefinition对象,调用了updateObjectDefinitionByDefinition方法。

      /**
      +    * Update definition
      +    *
      +    * @param ObjectDefinition $objDfn
      +    * @param array            $definition
      +    *
      +    * @return ObjectDefinition
      +    */
      +private function updateObjectDefinitionByDefinition(ObjectDefinition $objDfn, array $definition): ObjectDefinition
      +{
      +    [$constructInject, $propertyInjects, $option] = $this->parseDefinition($definition);
      +
      +    // Set construct inject
      +    if (!empty($constructInject)) {
      +        $objDfn->setConstructorInjection($constructInject);
      +    }
      +
      +    // Set property inject
      +    foreach ($propertyInjects as $propertyName => $propertyInject) {
      +        $objDfn->setPropertyInjection($propertyName, $propertyInject);
      +    }
      +
      +    $scopes = [
      +        Bean::SINGLETON,
      +        Bean::PROTOTYPE,
      +        Bean::REQUEST,
      +    ];
      +
      +    $scope = $option['scope'] ?? '';
      +    $alias = $option['alias'] ?? '';
      +
      +    if (!empty($scope) && !in_array($scope, $scopes, true)) {
      +        throw new InvalidArgumentException('Scope for definition is not undefined');
      +    }
      +
      +    // Update scope
      +    if (!empty($scope)) {
      +        $objDfn->setScope($scope);
      +    }
      +
      +    // Update alias
      +    if (!empty($alias)) {
      +        $objDfn->setAlias($alias);
      +
      +        $objAlias = $objDfn->getAlias();
      +        unset($this->aliases[$objAlias]);
      +
      +        $this->aliases[$alias] = $objDfn->getName();
      +    }
      +
      +    return $objDfn;
      +}
      +

      这里调用了parseDefinition方法进行解析。

      /**
      +    * Parse definition
      +    *
      +    * @param array $definition
      +    *
      +    * @return array
      +    */
      +private function parseDefinition(array $definition): array
      +{
      +    // Remove class key
      +    unset($definition['class']);
      +
      +    // Parse construct
      +    $constructArgs = $definition[0] ?? [];
      +    if (!is_array($constructArgs)) {
      +        throw new InvalidArgumentException('Construct args for definition must be array');
      +    }
      +
      +    // Parse construct args
      +    $argInjects = [];
      +    foreach ($constructArgs as $arg) {
      +        [$argValue, $argIsRef] = $this->getValueByRef($arg);
      +
      +        $argInjects[] = new ArgsInjection($argValue, $argIsRef);
      +    }
      +
      +    // Set construct inject
      +    $constructInject = null;
      +    if (!empty($argInjects)) {
      +        $constructInject = new MethodInjection('__construct', $argInjects);
      +    }
      +
      +    // Remove construct definition
      +    unset($definition[0]);
      +
      +    // Parse definition option
      +    $option = $definition['__option'] ?? [];
      +    if (!is_array($option)) {
      +        throw new InvalidArgumentException('__option for definition must be array');
      +    }
      +
      +    // Remove `__option`
      +    unset($definition['__option']);
      +
      +    // Parse definition properties
      +    $propertyInjects = [];
      +    foreach ($definition as $propertyName => $propertyValue) {
      +        if (!is_string($propertyName)) {
      +            throw new InvalidArgumentException('Property key from definition must be string');
      +        }
      +
      +        [$proValue, $proIsRef] = $this->getValueByRef($propertyValue);
      +
      +        // Parse property for array
      +        if (is_array($proValue)) {
      +            $proValue = $this->parseArrayProperty($proValue);
      +        }
      +
      +        $propertyInject = new PropertyInjection($propertyName, $proValue, $proIsRef);
      +
      +        $propertyInjects[$propertyName] = $propertyInject;
      +    }
      +
      +    return [$constructInject, $propertyInjects, $option];
      +}
      +

      解析__construct方法和传参,解析属性信息。

      回到updateObjectDefinitionByDefinition方法,将__construct和类属性信息注册到ObjectDefinition对象上,到这里parseDefinitions方法执行完毕。

      回到Container类的init方法,接下来调用了initializeBeans方法。

      /**
      +    * Initialize beans
      +    *
      +    * @throws InvalidArgumentException
      +    * @throws ReflectionException
      +    */
      +private function initializeBeans(): void
      +{
      +    /* @var ObjectDefinition $objectDefinition */
      +    foreach ($this->objectDefinitions as $beanName => $objectDefinition) {
      +        $scope = $objectDefinition->getScope();
      +        // Exclude request
      +        if ($scope === Bean::REQUEST) {
      +            $this->requestDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // Exclude session
      +        if ($scope === Bean::SESSION) {
      +            $this->sessionDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // New bean
      +        $this->newBean($beanName);
      +    }
      +}
      +

      对于scope不为Bean::REQUESTBean::SESSION的,调用newBean方法。

      /**
      +    * Initialize beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object
      +    * @throws ReflectionException
      +    */
      +private function newBean(string $beanName, string $id = '')
      +{
      +    // First, check bean whether has been create.
      +    if (isset($this->singletonPool[$beanName]) || isset($this->prototypePool[$beanName])) {
      +        return $this->get($beanName);
      +    }
      +
      +    // Get object definition
      +    $objectDefinition = $this->getNewObjectDefinition($beanName);
      +
      +    $scope     = $objectDefinition->getScope();
      +    $alias     = $objectDefinition->getAlias();
      +    $className = $objectDefinition->getClassName();
      +
      +    // Cache reflection class info
      +    Reflections::cache($className);
      +
      +    // Before initialize bean
      +    $this->beforeInit($beanName, $className, $objectDefinition);
      +
      +    $constructArgs   = [];
      +    $constructInject = $objectDefinition->getConstructorInjection();
      +    if ($constructInject !== null) {
      +        $constructArgs = $this->getConstructParams($constructInject, $id);
      +    }
      +
      +    $propertyInjects = $objectDefinition->getPropertyInjections();
      +
      +    // Proxy class
      +    if ($this->handler) {
      +        $className = $this->handler->classProxy($className);
      +    }
      +
      +    $reflectionClass = new ReflectionClass($className);
      +    $reflectObject   = $this->newInstance($reflectionClass, $constructArgs);
      +
      +    // Inject properties values
      +    $this->newProperty($reflectObject, $reflectionClass, $propertyInjects, $id);
      +
      +    // Alias
      +    if (!empty($alias)) {
      +        $this->aliases[$alias] = $beanName;
      +    }
      +
      +    // Call init method if exist
      +    if ($reflectionClass->hasMethod(self::INIT_METHOD)) {
      +        $reflectObject->{self::INIT_METHOD}();
      +    }
      +
      +    return $this->setNewBean($beanName, $scope, $reflectObject, $id);
      +}
      +

      通过反射实例化Bean对应的类,注册对应的属性。

      如果类存在self::INIT_METHOD方法,执行此方法。

      /**
      +    * @param string $beanName
      +    * @param string $scope
      +    * @param object $object
      +    * @param string $id
      +    *
      +    * @return object
      +    */
      +private function setNewBean(string $beanName, string $scope, $object, string $id = '')
      +{
      +    switch ($scope) {
      +        case Bean::SINGLETON: // Singleton
      +            $this->singletonPool[$beanName] = $object;
      +            break;
      +        case Bean::PROTOTYPE:
      +            $this->prototypePool[$beanName] = $object;
      +            // Clone it
      +            $object = clone $object;
      +            break;
      +        case Bean::REQUEST:
      +            $this->requestPool[$id][$beanName] = $object;
      +            break;
      +        case Bean::SESSION:
      +            $this->sessionPool[$id][$beanName] = $object;
      +            break;
      +    }
      +
      +    return $object;
      +}
      +

      setNewBean方法,根据对应的scope信息,将实例化后的反射类注册到对应的类属性上。

      到这里BeanProcessor类就执行完了。

      💬评论
      \ No newline at end of file diff --git a/posts/swoft-console-processor-analysis/index.html b/posts/swoft-console-processor-analysis/index.html new file mode 100644 index 000000000..1a23d6061 --- /dev/null +++ b/posts/swoft-console-processor-analysis/index.html @@ -0,0 +1,953 @@ +Swoft 框架运行分析(五) —— ConsoleProcessor模块分析 | 流动 +

      Swoft 框架运行分析(五) —— ConsoleProcessor模块分析

      这里以Swoft启动http server为例。

      php bin/swoft http:start

      执行上述命令,启动http server。

      在前面第一篇文章的时候,提到了如何启动http服务。

      今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。

      /**
      +    * Handle console
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeConsole()) {
      +        return false;
      +    }
      +
      +    /** @var Router $router */
      +    $router = bean('cliRouter');
      +
      +    // Register console routes
      +    CommandRegister::register($router);
      +
      +    CLog::info(
      +        'Console command route registered (group %d, command %d)',
      +        $router->groupCount(),
      +        $router->count()
      +    );
      +
      +    // Run console application
      +    bean('cliApp')->run();
      +
      +    return $this->application->afterConsole();
      +}
      +

      这里调用了bean方法获取Bean实例,定义见swoft-component-2.0.5\src\bean\src\Helper\Functions.php

      if (!function_exists('bean')) {
      +    /**
      +     * Get bean by name
      +     *
      +     * @param string $name Bean name Or alias Or class name
      +     *
      +     * @return object|string|mixed
      +     */
      +    function bean(string $name)
      +    {
      +        if (BeanFactory::isSingleton('config')) {
      +            return BeanFactory::getBean($name);
      +        }
      +
      +        return sprintf('${%s}', $name);
      +    }
      +}
      +

      这里调用了BeanFactorygetBean方法。

      /**
      +    * Get object by name
      +    *
      +    * @param string $name Bean name Or alias Or class name
      +    *
      +    * @return object|mixed
      +    */
      +public static function getBean(string $name)
      +{
      +    return Container::getInstance()->get($name);
      +}
      +

      最终调用的是Swoft\Bean\Container下的get方法。

      /**
      +    * Finds an entry of the container by its identifier and returns it.
      +    *
      +    * @param string $id Bean name Or alias Or class name
      +    *
      +    * When class name will return all of instance for class name
      +    *
      +    * @return object
      +    * @throws InvalidArgumentException
      +    */
      +public function get($id)
      +{
      +    // It is singleton
      +    if (isset($this->singletonPool[$id])) {
      +        return $this->singletonPool[$id];
      +    }
      +
      +    // Prototype by clone
      +    if (isset($this->prototypePool[$id])) {
      +        return clone $this->prototypePool[$id];
      +    }
      +
      +    // Alias name
      +    $aliasId = $this->aliases[$id] ?? '';
      +    if ($aliasId) {
      +        return $this->get($aliasId);
      +    }
      +
      +    // Class name
      +    $classNames = $this->classNames[$id] ?? [];
      +    if ($classNames) {
      +        $id = end($classNames);
      +        return $this->get($id);
      +    }
      +
      +    // Interface
      +    if (interface_exists($id)) {
      +        $id = InterfaceRegister::getInterfaceInjectBean($id);
      +        return $this->get($id);
      +    }
      +
      +    // Not defined
      +    if (!isset($this->objectDefinitions[$id])) {
      +        throw new InvalidArgumentException(sprintf('The bean of %s is not defined', $id));
      +    }
      +
      +    /* @var ObjectDefinition $objectDefinition */
      +    $objectDefinition = $this->objectDefinitions[$id];
      +
      +    // Prototype
      +    return $this->safeNewBean($objectDefinition->getName());
      +}
      +

      获取对应的ObjectDefinition实例,然后调用safeNewBean方法。

      /**
      +    * Secure creation of beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object|mixed
      +    */
      +private function safeNewBean(string $beanName, string $id = '')
      +{
      +    try {
      +        return $this->newBean($beanName, $id);
      +    } catch (Throwable $e) {
      +        throw new InvalidArgumentException($e->getMessage(), 500, $e);
      +    }
      +}
      +

      这里又调用了newBean方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的Bean类。

      cliRouter对应的类是说明呢?这个定义在swoft-component-2.0.5\src\console\src\AutoLoader.php里。

      /**
      +    * {@inheritDoc}
      +    */
      +public function beans(): array
      +{
      +    return [
      +        'cliApp'    => [
      +            'class'   => Application::class,
      +            'version' => '2.0.0'
      +        ],
      +        'cliRouter' => [
      +            'class' => Router::class,
      +        ],
      +        'cliDispatcher' => [
      +            'class' => ConsoleDispatcher::class,
      +        ],
      +    ];
      +}
      +

      所以$router = bean('cliRouter'),返回的是一个Swoft\Console\Router\Router类。

      回到ConsoleProcessor类,接着看代码。

      CommandRegister::register($router);
      +

      调用了CommandRegister类的register方法。

      
      +/**
      +    * @param Router $router
      +    * @throws ReflectionException
      +    */
      +public static function register(Router $router): void
      +{
      +    $maxLen  = 12;
      +    $groups  = [];
      +    $docOpts = [
      +        'allow' => ['example']
      +    ];
      +    $defInfo = [
      +        'example'     => '',
      +        'description' => 'No description message',
      +    ];
      +
      +    foreach (self::$commands as $class => $mapping) {
      +        $names = [];
      +        $group = $mapping['group'];
      +        // Set ID aliases
      +        $router->setIdAliases($mapping['idAliases']);
      +        // Set group name aliases
      +        $router->setGroupAliases($group, $mapping['aliases']);
      +
      +        $refInfo = Swoft::getReflection($class);
      +        $mhdInfo = $refInfo['methods'] ?? [];
      +        $grpOpts = $mapping['options'] ?? [];
      +
      +        foreach ($mapping['commands'] as $method => $route) {
      +            // $method = $route['method'];
      +            $cmdDesc = $route['desc'];
      +            $command = $route['command'];
      +
      +            $idLen = strlen($group . $command);
      +            if ($idLen > $maxLen) {
      +                $maxLen = $idLen;
      +            }
      +
      +            $cmdExam = '';
      +            if (!empty($mhdInfo[$method]['comments'])) {
      +                $tagInfo = DocBlock::getTags($mhdInfo[$method]['comments'], $docOpts, $defInfo);
      +                $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo['description']);
      +                $cmdExam = $tagInfo['example'];
      +            }
      +
      +            $route['group']   = $group;
      +            $route['desc']    = ucfirst($cmdDesc);
      +            $route['example'] = $cmdExam;
      +            $route['options'] = self::mergeOptions($grpOpts, $route['options']);
      +            // Append group option
      +            $route['enabled']   = $mapping['enabled'];
      +            $route['coroutine'] = $mapping['coroutine'];
      +
      +            $router->map($group, $command, [$class, $method], $route);
      +            $names[] = $command;
      +        }
      +
      +        $groupExam = '';
      +        $groupDesc = $mapping['desc'];
      +        if (!empty($refInfo['comments'])) {
      +            $tagInfo   = DocBlock::getTags($refInfo['comments'], $docOpts, $defInfo);
      +            $groupDesc = $groupDesc ?: Str::firstLine($tagInfo['description']);
      +            $groupExam = $tagInfo['example'];
      +        }
      +
      +        $groups[$group] = [
      +            'names'   => $names,
      +            'desc'    => ucfirst($groupDesc),
      +            'class'   => $class,
      +            'alias'   => $mapping['alias'],
      +            'aliases' => $mapping['aliases'],
      +            'example' => $groupExam,
      +        ];
      +    }
      +
      +    $router->setGroups($groups);
      +    // +1 because router->delimiter
      +    $router->setKeyWidth($maxLen + 1);
      +    // clear data
      +    self::$commands = [];
      +}
      +

      这里遍历了类属性$commands注册路由。

      那么$commands这个属性是哪里来的呢?

      既然开头我们说的是http服务是怎么启动的,这里我们就以http-server来举例,找到swoft-component-2.0.5\src\http-server\src\Command\HttpServerCommand.php文件。

      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server\Command;
      +
      +use ReflectionException;
      +use Swoft;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Console\Annotation\Mapping\Command;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\Annotation\Mapping\CommandOption;
      +use Swoft\Console\Helper\Show;
      +use Swoft\Http\Server\HttpServer;
      +use Swoft\Server\Command\BaseServerCommand;
      +use Swoft\Server\Exception\ServerException;
      +use function bean;
      +use function input;
      +use function output;
      +
      +/**
      + * Provide some commands to manage the swoft HTTP server
      + *
      + * @since 2.0
      + *
      + * @Command("http", alias="httpsrv", coroutine=false)
      + * @example
      + *  {fullCmd}:start     Start the http server
      + *  {fullCmd}:stop      Stop the http server
      + */
      +class HttpServerCommand extends BaseServerCommand
      +{
      +    /**
      +     * Start the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +     * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @throws ServerException
      +     * @example
      +     *   {fullCommand}
      +     *   {fullCommand} -d
      +     *
      +     */
      +    public function start(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $masterPid = $server->getPid();
      +            output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +            return;
      +        }
      +
      +        // Startup settings
      +        $this->configStartOption($server);
      +
      +        $settings = $server->getSetting();
      +        // Setting
      +        $workerNum = $settings['worker_num'];
      +
      +        // Server startup parameters
      +        $mainHost = $server->getHost();
      +        $mainPort = $server->getPort();
      +        $modeName = $server->getModeName();
      +        $typeName = $server->getTypeName();
      +
      +        // Http
      +        $panel = [
      +            'HTTP' => [
      +                'listen' => $mainHost . ':' . $mainPort,
      +                'type'   => $typeName,
      +                'mode'   => $modeName,
      +                'worker' => $workerNum,
      +            ],
      +        ];
      +
      +        // Port Listeners
      +        $panel = $this->appendPortsToPanel($server, $panel);
      +
      +        Show::panel($panel);
      +
      +        output()->writeln('<success>HTTP server start success !</success>');
      +
      +        // Start the server
      +        $server->start();
      +    }
      +
      +    /**
      +     * Reload worker processes
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-t]")
      +     * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function reload(): void
      +    {
      +        $server = $this->createServer();
      +        $script = input()->getScript();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot reload</error>');
      +            return;
      +        }
      +
      +        output()->writef('<info>Server %s is reloading</info>', $script);
      +
      +        if ($reloadTask = input()->hasOpt('t')) {
      +            Show::notice('Will only reload task worker');
      +        }
      +
      +        if (!$server->reload($reloadTask)) {
      +            Show::error('The swoole server worker process reload fail!');
      +            return;
      +        }
      +
      +        output()->writef('<success>HTTP server %s reload success</success>', $script);
      +    }
      +
      +    /**
      +     * Stop the currently running server
      +     *
      +     * @CommandMapping()
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function stop(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot stop.</error>');
      +            return;
      +        }
      +
      +        // Do stopping.
      +        $server->stop();
      +    }
      +
      +    /**
      +     * Restart the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
      +     * @CommandOption("daemon", short="d", desc="Run server on the background")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @example
      +     *  {fullCommand}
      +     *  {fullCommand} -d
      +     */
      +    public function restart(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $success = $server->stop();
      +
      +            if (!$success) {
      +                output()->error('Stop the old server failed!');
      +                return;
      +            }
      +        }
      +
      +        output()->writef('<success>Server HTTP restart success !</success>');
      +        $server->startWithDaemonize();
      +    }
      +
      +    /**
      +     * @return HttpServer
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    private function createServer(): HttpServer
      +    {
      +        $script  = input()->getScript();
      +        $command = $this->getFullCommand();
      +
      +        /** @var HttpServer $server */
      +        $server = bean('httpServer');
      +        $server->setScriptFile(Swoft::app()->getPath($script));
      +        $server->setFullCommand($command);
      +
      +        return $server;
      +    }
      +}
      +

      通过Swoft文档,我们可以看到这里分别使用了类注解和方法注解。

      @Command("http", alias="httpsrv", coroutine=false)
      +@CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +@CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +...
      +

      通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。

      这里以Swoft\Console\Annotation\Mapping\CommandMapping这个注解为例,对应的注解解析类为Swoft\Console\Annotation\Parser\CommandMappingParser

      <?php declare(strict_types=1);
      +
      +namespace Swoft\Console\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\CommandRegister;
      +
      +/**
      + * Class CommandMappingParser
      + *
      + * @since 2.0
      + * @AnnotationParser(CommandMapping::class)
      + */
      +class CommandMappingParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int            $type Class or Method or Property
      +     * @param CommandMapping $annotation Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_METHOD) {
      +            throw new AnnotationException('`@CommandMapping` must be defined on class method!');
      +        }
      +
      +        $method = $this->methodName;
      +
      +        // add route info for controller action
      +        CommandRegister::addRoute($this->className, $method, [
      +            'command' => $annotation->getName() ?: $method,
      +            'method'  => $method,
      +            'alias'   => $annotation->getAlias(),
      +            'aliases' => $annotation->getAliases(),
      +            'desc'    => $annotation->getDesc(),
      +            'usage'   => $annotation->getUsage(),
      +            // 'example' => $annotation->getExample(),
      +        ]);
      +
      +        return [];
      +    }
      +}
      +

      看到这里,你应该可以猜到CommandRegister类的$commands是怎么来的了吧。

      我们看下CommandRegister类的addRoute方法,验证下想法。

      /**
      +    * @param string $class
      +    * @param string $method
      +    * @param array  $route
      +    *
      +    * @throws AnnotationException
      +    */
      +public static function addRoute(string $class, string $method, array $route): void
      +{
      +    self::checkClass($class);
      +
      +    // init some keys
      +    $route['options']   = [];
      +    $route['arguments'] = [];
      +    // save
      +    self::$commands[$class]['commands'][$method] = $route;
      +}
      +

      bingo,跟我们猜想的一模一样,这下我们也知道CommandMapping这个注解是用来注册终端的路由信息。

      回到ConsoleProcessor类,接着看代码。

      CLog::info(
      +    'Console command route registered (group %d, command %d)',
      +    $router->groupCount(),
      +    $router->count()
      +);
      +

      打印日志。

      // Run console application
      +bean('cliApp')->run();
      +

      感觉到了重头戏。

      根据前面的代码,我们知道cliApp这个Bean实例对应的类是Swoft\Console\Application

      /**
      +    * @return void
      +    * @throws ContainerException
      +    */
      +public function run(): void
      +{
      +    try {
      +        Swoft::trigger(ConsoleEvent::RUN_BEFORE, $this);
      +
      +        // Prepare
      +        $this->prepare();
      +
      +        // Get input command
      +        $inputCommand = $this->input->getCommand();
      +
      +        if (!$inputCommand) {
      +            $this->filterSpecialOption();
      +        } else {
      +            $this->doRun($inputCommand);
      +        }
      +
      +        Swoft::trigger(ConsoleEvent::RUN_AFTER, $this, $inputCommand);
      +    } catch (Throwable $e) {
      +        /** @var ConsoleErrorDispatcher $errDispatcher */
      +        $errDispatcher = BeanFactory::getSingleton(ConsoleErrorDispatcher::class);
      +
      +        // Handle request error
      +        $errDispatcher->run($e);
      +    }
      +}
      +

      通过Swoft::trigger,注册了ConsoleEvent::RUN_BEFOREConsoleEvent::RUN_AFTER两个事件。

      protected function prepare(): void
      +{
      +    $this->input  = \input();
      +    $this->output = \output();
      +
      +    // load builtin comments vars
      +    $this->setCommentsVars($this->commentsVars());
      +}
      +

      prepare比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。

      $inputCommand = $this->input->getCommand();
      +if (!$inputCommand) {
      +    $this->filterSpecialOption();
      +} else {
      +    $this->doRun($inputCommand);
      +}
      +

      获取终端命令行下的输入,如果有输入执行doRun方法。

      /**
      +    * @param string $inputCmd
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws Throwable
      +    */
      +protected function doRun(string $inputCmd): void
      +{
      +    $output = $this->output;
      +    /* @var Router $router */
      +    $router = Swoft::getBean('cliRouter');
      +    $result = $router->match($inputCmd);
      +
      +    // Command not found
      +    if ($result[0] === Router::NOT_FOUND) {
      +        $names = $router->getAllNames();
      +        $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +        // find similar command names by similar_text()
      +        if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +            $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +        } else {
      +            $this->showApplicationHelp(false);
      +        }
      +        return;
      +    }
      +
      +    $info = $result[1];
      +
      +    // Only input a group name, display help for the group
      +    if ($result[0] === Router::ONLY_GROUP) {
      +        $this->showGroupHelp($info['group']);
      +        return;
      +    }
      +
      +    // Display help for a command
      +    if ($this->input->getSameOpt(['h', 'help'])) {
      +        $this->showCommandHelp($info);
      +        return;
      +    }
      +
      +    // Parse default options and arguments
      +    $this->bindCommandFlags($info);
      +    $this->input->setCommandId($info['cmdId']);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +
      +    // Call command handler
      +    /** @var ConsoleDispatcher $dispatcher */
      +    $dispatcher = Swoft::getSingleton('cliDispatcher');
      +    $dispatcher->dispatch($info);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, $this, $info);
      +}
      +
      $router = Swoft::getBean('cliRouter');
      +$result = $router->match($inputCmd);
      +

      获取cliRouter实例,根据输入匹配路由操作类。

      /**
      +    * Match route by input command
      +    *
      +    * @param array $params [$route]
      +    *
      +    * @return array
      +    *
      +    * [
      +    *  status, info(array)
      +    * ]
      +    */
      +public function match(...$params): array
      +{
      +    $delimiter = $this->delimiter;
      +    $inputCmd  = trim($params[0], "$delimiter ");
      +    $noSepChar = strpos($inputCmd, $delimiter) === false;
      +
      +    // If use command ID alias
      +    if ($noSepChar && isset($this->idAliases[$inputCmd])) {
      +        $inputCmd = $this->idAliases[$inputCmd];
      +        // Must re-check
      +        $noSepChar = strpos($inputCmd, $delimiter) === false;
      +    }
      +
      +    if ($noSepChar && in_array($inputCmd, $this->defaultCommands, true)) {
      +        $group   = $this->defaultGroup;
      +        $command = $this->resolveCommandAlias($inputCmd);
      +
      +        // Only a group name
      +    } elseif ($noSepChar) {
      +        $group = $this->resolveGroupAlias($inputCmd);
      +
      +        if (isset($this->groups[$group])) {
      +            return [self::ONLY_GROUP, ['group' => $group]];
      +        }
      +
      +        return [self::NOT_FOUND];
      +    } else {
      +        $nameList = explode($delimiter, $inputCmd, 2);
      +
      +        if (count($nameList) === 2) {
      +            [$group, $command] = $nameList;
      +            // resolve command alias
      +            $command = $this->resolveCommandAlias($command);
      +        } else {
      +            $command = '';
      +            // $command = $this->defaultCommand;
      +            $group = $nameList[0];
      +        }
      +    }
      +
      +    $group = $this->resolveGroupAlias($group);
      +    // build command ID
      +    $commandID = $this->buildCommandID($group, $command);
      +
      +    if (isset($this->routes[$commandID])) {
      +        $info = $this->routes[$commandID];
      +        // append some info
      +        $info['cmdId'] = $commandID;
      +
      +        return [self::FOUND, $info];
      +    }
      +
      +    if ($group && isset($this->groups[$group])) {
      +        return [self::ONLY_GROUP, ['group' => $group]];
      +    }
      +
      +    return [self::NOT_FOUND];
      +}
      +

      这里会返回匹配后的路由信息。

      回到doRun方法。

      // Command not found
      +if ($result[0] === Router::NOT_FOUND) {
      +    $names = $router->getAllNames();
      +    $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +    // find similar command names by similar_text()
      +    if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +        $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +    } else {
      +        $this->showApplicationHelp(false);
      +    }
      +    return;
      +}
      +
      +$info = $result[1];
      +
      +// Only input a group name, display help for the group
      +if ($result[0] === Router::ONLY_GROUP) {
      +    $this->showGroupHelp($info['group']);
      +    return;
      +}
      +
      +// Display help for a command
      +if ($this->input->getSameOpt(['h', 'help'])) {
      +    $this->showCommandHelp($info);
      +    return;
      +}
      +

      根据返回的路由信息进行不同的处理。

      // Parse default options and arguments
      +$this->bindCommandFlags($info);
      +$this->input->setCommandId($info['cmdId']);
      +
      +Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +

      绑定默认参数,注册ConsoleEvent::DISPATCH_BEFORE事件。

      // Call command handler
      +/** @var ConsoleDispatcher $dispatcher */
      +$dispatcher = Swoft::getSingleton('cliDispatcher');
      +$dispatcher->dispatch($info);
      +

      获取cliDispatcherBean实例,对应Swoft\Console\ConsoleDispatcher类,调用dispatch方法。

      /**
      +    * @param array $params
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws Throwable
      +    */
      +public function dispatch(...$params): void
      +{
      +    $route = $params[0];
      +    // Handler info
      +    [$className, $method] = $route['handler'];
      +
      +    // Bind method params
      +    $params = $this->getBindParams($className, $method);
      +    $object = Swoft::getSingleton($className);
      +
      +    // Blocking running
      +    if (!$route['coroutine']) {
      +        $this->before(get_parent_class($object), $method);
      +        PhpHelper::call([$object, $method], ...$params);
      +        $this->after($method);
      +        return;
      +    }
      +
      +    // Hook php io function
      +    Runtime::enableCoroutine();
      +
      +    // If in unit test env, has been in coroutine.
      +    if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
      +        $this->executeByCo($object, $method, $params);
      +        return;
      +    }
      +
      +    // Coroutine running
      +    srun(function () use ($object, $method, $params) {
      +        $this->executeByCo($object, $method, $params);
      +    });
      +}
      +

      获取路由对应的类和方法,通过Swoft::getSingleton($className);实例化对象。

      如果未开启协程,则用PhpHelper::call([$object, $method], ...$params);调用对应的方法。

      开启协程的话,使用$this->executeByCo($object, $method, $params);调用对应的方法。

      我们前面启动命令是php bin/swoft http:start,这里对应的类就是Swoft\Http\Server\Command\HttpServerCommand,方法就是start

      /**
      +    * Start the http server
      +    *
      +    * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +    * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +    *
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws ServerException
      +    * @example
      +    *   {fullCommand}
      +    *   {fullCommand} -d
      +    *
      +    */
      +public function start(): void
      +{
      +    $server = $this->createServer();
      +
      +    // Check if it has started
      +    if ($server->isRunning()) {
      +        $masterPid = $server->getPid();
      +        output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +        return;
      +    }
      +
      +    // Startup settings
      +    $this->configStartOption($server);
      +
      +    $settings = $server->getSetting();
      +    // Setting
      +    $workerNum = $settings['worker_num'];
      +
      +    // Server startup parameters
      +    $mainHost = $server->getHost();
      +    $mainPort = $server->getPort();
      +    $modeName = $server->getModeName();
      +    $typeName = $server->getTypeName();
      +
      +    // Http
      +    $panel = [
      +        'HTTP' => [
      +            'listen' => $mainHost . ':' . $mainPort,
      +            'type'   => $typeName,
      +            'mode'   => $modeName,
      +            'worker' => $workerNum,
      +        ],
      +    ];
      +
      +    // Port Listeners
      +    $panel = $this->appendPortsToPanel($server, $panel);
      +
      +    Show::panel($panel);
      +
      +    output()->writeln('<success>HTTP server start success !</success>');
      +
      +    // Start the server
      +    $server->start();
      +}
      +

      这里先调用了createServer方法。

      /**
      +    * @return HttpServer
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +private function createServer(): HttpServer
      +{
      +    $script  = input()->getScript();
      +    $command = $this->getFullCommand();
      +
      +    /** @var HttpServer $server */
      +    $server = bean('httpServer');
      +    $server->setScriptFile(Swoft::app()->getPath($script));
      +    $server->setFullCommand($command);
      +
      +    return $server;
      +}
      +

      获取httpServerBean实例。

      框架定义在swoft-component-2.0.5\src\http-server\src\AutoLoader.php,这里声明了onRequest回调事件。

      'httpServer'      => [
      +    'on' => [
      +        SwooleEvent::REQUEST => bean(RequestListener::class)
      +    ]
      +],
      +

      业务定义在swoft-2.0.5\app\bean.php

      'httpServer'        => [
      +    'class'    => HttpServer::class,
      +    'port'     => 18306,
      +    'listener' => [
      +        'rpc' => bean('rpcServer')
      +    ],
      +    'process'  => [
      +//            'monitor' => bean(MonitorProcess::class)
      +//            'crontab' => bean(CrontabProcess::class)
      +    ],
      +    'on'       => [
      +//            SwooleEvent::TASK   => bean(SyncTaskListener::class),  // Enable sync task
      +        SwooleEvent::TASK   => bean(TaskListener::class),  // Enable task must task and finish event
      +        SwooleEvent::FINISH => bean(FinishListener::class)
      +    ],
      +    /* @see HttpServer::$setting */
      +    'setting'  => [
      +        'task_worker_num'       => 12,
      +        'task_enable_coroutine' => true
      +    ]
      +],
      +

      createServer返回的是一个Swoft\Http\Server\HttpServer实例。

      回到HttpServerCommand类的start方法。

      // Start the server
      +$server->start();
      +

      调用Swoft\Http\Server\HttpServer类的start方法。

      /**
      +    * Start server
      +    *
      +    * @throws ServerException
      +    * @throws ContainerException
      +    */
      +public function start(): void
      +{
      +    $this->swooleServer = new \Swoole\Http\Server($this->host, $this->port, $this->mode, $this->type);
      +    $this->startSwoole();
      +}
      +

      声明Swoole\Http\Server对象,调用startSwoole方法。

      Swoft\Http\Server\HttpServer类继承自Swoft\Server\Server类,startSwoole方法定义在这个类。

      /**
      +    * Bind swoole event and start swoole server
      +    *
      +    * @throws ServerException
      +    * @throws Swoft\Bean\Exception\ContainerException
      +    */
      +protected function startSwoole(): void
      +{
      +    if (!$this->swooleServer) {
      +        throw new ServerException('You must to new server before start swoole!');
      +    }
      +
      +    // Always enable coroutine hook on server
      +    CLog::info('Swoole\Runtime::enableCoroutine');
      +    Runtime::enableCoroutine();
      +
      +    Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);
      +
      +    // Set settings
      +    $this->swooleServer->set($this->setting);
      +    // Update setting property
      +    // $this->setSetting($this->swooleServer->setting);
      +
      +    // Before Add event
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);
      +
      +    // Register events
      +    $defaultEvents = $this->defaultEvents();
      +    $swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +    // Add events
      +    $this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +
      +    //After add event
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);
      +
      +    // Before listener
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);
      +
      +    // Add port listener
      +    $this->addListener();
      +
      +    // Before bind process
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);
      +
      +    // Add Process
      +    Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);
      +
      +    // After bind process
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);
      +
      +    // Trigger event
      +    Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));
      +
      +    // Storage server instance
      +    self::$server = $this;
      +
      +    // Start swoole server
      +    $this->swooleServer->start();
      +}
      +
      $this->swooleServer->set($this->setting);
      +

      设置Swoole运行配置。

      // Register events
      +$defaultEvents = $this->defaultEvents();
      +$swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +// Add events
      +$this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +

      添加Swoole回调事件。

      // Add port listener
      +$this->addListener();
      +

      监听端口。

      // Start swoole server
      +$this->swooleServer->start();
      +

      启动Swoole\Http\Server服务。

      现在服务已经启动了,那http请求是怎么被处理的呢?

      这个我们下一篇再继续讲。

      💬评论
      \ No newline at end of file diff --git a/posts/swoft-event-processor-analysis/index.html b/posts/swoft-event-processor-analysis/index.html new file mode 100644 index 000000000..eea094b87 --- /dev/null +++ b/posts/swoft-event-processor-analysis/index.html @@ -0,0 +1,389 @@ +Swoft 框架运行分析(四) —— EventProcessor模块分析 | 流动 +

      Swoft 框架运行分析(四) —— EventProcessor模块分析

      今天我们来看一下EventProcessor的实现。

      /**
      +    * Handle event register
      +    * @return bool
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeEvent()) {
      +        CLog::warning('Stop event processor by beforeEvent return false');
      +        return false;
      +    }
      +
      +    /** @var EventManager $eventManager */
      +    $eventManager = bean('eventManager');
      +    [$count1, $count2] = ListenerRegister::register($eventManager);
      +
      +    CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2);
      +
      +    // Trigger a app init event
      +    Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +
      +    return $this->application->afterEvent();
      +}
      +

      获取eventManagerBean实例,对应为Swoft\Event\Manager\EventManager类。

      [$count1, $count2] = ListenerRegister::register($eventManager);
      +

      调用ListenerRegister类的register方法。

      /**
      +    * @param EventManager $em
      +    *
      +    * @return array
      +    */
      +public static function register(EventManager $em): array
      +{
      +    foreach (self::$listeners as $className => $eventInfo) {
      +        $listener = Swoft::getSingleton($className);
      +
      +        if (!$listener instanceof EventHandlerInterface) {
      +            throw new RuntimeException("The event listener class '{$className}' must be instanceof EventHandlerInterface");
      +        }
      +
      +        $em->addListener($listener, $eventInfo);
      +    }
      +
      +    foreach (self::$subscribers as $className) {
      +        $subscriber = Swoft::getSingleton($className);
      +        if (!$subscriber instanceof EventSubscriberInterface) {
      +            throw new RuntimeException("The event subscriber class '{$className}' must be instanceof EventSubscriberInterface");
      +        }
      +
      +        $em->addSubscriber($subscriber);
      +    }
      +
      +    $count1 = count(self::$listeners);
      +    $count2 = count(self::$subscribers);
      +    // Clear data
      +    self::$listeners = self::$subscribers = [];
      +
      +    return [$count1, $count2];
      +}
      +

      遍历ListenerRegister类下的$listeners$subscribers属性,绑定事件到eventManagerBean实例上。

      这里的$listeners$subscribers是从哪里来的呢?

      这里以http-server为例。

      swoft-component-2.0.5\src\http-server\src\Listener目录下,存在下面三个文件。

      AfterRequestListener.php
      +AppInitCompleteListener.php
      +BeforeRequestListener.php
      +

      这里我们以AppInitCompleteListener.php为例。

      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      可以看到这里通过@Listener(SwoftEvent::APP_INIT_COMPLETE),使用了Swoft\Event\Annotation\Mapping\Listener类注解,对应的注解解析类为Swoft\Event\Annotation\Parser\ListenerParser

      <?php declare(strict_types=1);
      +
      +namespace Swoft\Event\Annotation\Parser;
      +
      +use Doctrine\Common\Annotations\AnnotationException;
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\ListenerRegister;
      +
      +/**
      + * Class ListenerParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(Listener::class)
      + */
      +class ListenerParser extends Parser
      +{
      +    /**
      +     * @param int      $type
      +     * @param Listener $annotation
      +     *
      +     * @return array
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@Listener` must be defined on class!');
      +        }
      +
      +        // collect listeners
      +        ListenerRegister::addListener($this->className, [
      +            // event name => listener priority
      +            $annotation->getEvent() => $annotation->getPriority()
      +        ]);
      +
      +        return [$this->className, $this->className, Bean::SINGLETON, ''];
      +    }
      +}
      +
      /**
      +    * @param string $className
      +    * @param array  $definition [event name => listener priority]
      +    */
      +public static function addListener(string $className, array $definition = []): void
      +{
      +    // Collect listeners
      +    self::$listeners[$className] = $definition;
      +}
      +

      可以看到这里通过ListenerRegister::addListener方法,往ListenerRegister上注册了$listeners属性。

      属性$listeners$subscribers的值,都是通过注解解析得来。

      这里我们回到EventProcessor类的handle方法。

      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      trigger的方法定义如下。

      /**
      +    * Trigger an swoft application event
      +    *
      +    * @param string|EventInterface $event eg: 'app.start' 'app.stop'
      +    * @param null|mixed            $target
      +    * @param array                 $params
      +    *
      +    * @return EventInterface
      +    */
      +public static function trigger($event, $target = null, ...$params): EventInterface
      +{
      +    /** @see EventManager::trigger() */
      +    return BeanFactory::getSingleton('eventManager')->trigger($event, $target, $params);
      +}
      +

      这里调用了eventManager这个Bean实例的trigger方法。

      /**
      +    * Trigger an event. Can accept an EventInterface or will create one if not passed
      +    *
      +    * @param string|EventInterface $event  'app.start' 'app.stop'
      +    * @param mixed|string          $target It is object or string.
      +    * @param array|mixed           $args
      +    *
      +    * @return EventInterface
      +    * @throws InvalidArgumentException
      +    */
      +public function trigger($event, $target = null, array $args = []): EventInterface
      +{
      +    if ($isString = is_string($event)) {
      +        $name = trim($event);
      +    } elseif ($event instanceof EventInterface) {
      +        $name = trim($event->getName());
      +    } else {
      +        throw new InvalidArgumentException('Invalid event params for trigger event handler');
      +    }
      +
      +    $shouldCall = [];
      +
      +    // Have matched listener
      +    if (isset($this->listenedEvents[$name])) {
      +        $shouldCall[$name] = '';
      +    }
      +
      +    // Like 'app.db.query' => prefix: 'app.db'
      +    if ($pos = strrpos($name, '.')) {
      +        $prefix = substr($name, 0, $pos);
      +
      +        // Have a wildcards listener. eg 'app.db.*'
      +        $wildcardEvent = $prefix . '.*';
      +        if (isset($this->listenedEvents[$wildcardEvent])) {
      +            $shouldCall[$wildcardEvent] = substr($name, $pos + 1);
      +        }
      +    }
      +
      +    // Not found listeners
      +    if (!$shouldCall) {
      +        return $isString ? $this->basicEvent : $event;
      +    }
      +
      +    /** @var EventInterface $event */
      +    if ($isString) {
      +        $event = $this->events[$name] ?? $this->basicEvent;
      +    }
      +
      +    // Initial value
      +    $event->setName($name);
      +    $event->setParams($args);
      +    $event->setTarget($target);
      +    $event->stopPropagation(false);
      +
      +    // Notify event listeners
      +    foreach ($shouldCall as $name => $method) {
      +        $this->triggerListeners($this->listeners[$name], $event, $method);
      +
      +        if ($event->isPropagationStopped()) {
      +            return $event;
      +        }
      +    }
      +
      +    // Have global wildcards '*' listener.
      +    if (isset($this->listenedEvents['*'])) {
      +        $this->triggerListeners($this->listeners['*'], $event);
      +    }
      +
      +    return $event;
      +}
      +

      如果存在对应的事件,调用triggerListeners方法。

      /**
      +    * @param array|ListenerQueue $listeners
      +    * @param EventInterface      $event
      +    * @param string              $method
      +    */
      +protected function triggerListeners($listeners, EventInterface $event, string $method = ''): void
      +{
      +    // $handled = false;
      +    $name     = $event->getName();
      +    $callable = false === strpos($name, '.');
      +
      +    // 循环调用监听器,处理事件
      +    foreach ($listeners as $listener) {
      +        if ($event->isPropagationStopped()) {
      +            break;
      +        }
      +
      +        if (is_object($listener)) {
      +            if ($listener instanceof EventHandlerInterface) {
      +                $listener->handle($event);
      +            } elseif ($method && method_exists($listener, $method)) {
      +                $listener->$method($event);
      +            } elseif ($callable && method_exists($listener, $name)) {
      +                $listener->$name($event);
      +            } elseif (method_exists($listener, '__invoke')) {
      +                $listener($event);
      +            }
      +        } elseif (is_callable($listener)) {
      +            $listener($event);
      +        }
      +    }
      +}
      +

      遍历事件回调,执行对应方法。

      回到EventProcessor类的handle方法。

      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      这里的事件为SwoftEvent::APP_INIT_COMPLETE,所以这里会执行这个事件下的所有回调。

      这里以Swoft\Http\Server\Listener\AppInitCompleteListener为例。

      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      这里使用了Swoft\Event\Annotation\Mapping\Listener注解,对应的事件为SwoftEvent::APP_INIT_COMPLETE

      按照上面的分析,这里会调用到AppInitCompleteListenerhandle方法,获取httpRouterBean实例,注册http服务的路由信息和中间件。

      到这里,我们大概清楚了EventProcessor这个模块的作用,注册了所有事件的回调。

      💬评论
      \ No newline at end of file diff --git a/posts/swoft-execution-analysis/index.html b/posts/swoft-execution-analysis/index.html new file mode 100644 index 000000000..df66b7493 --- /dev/null +++ b/posts/swoft-execution-analysis/index.html @@ -0,0 +1,217 @@ +Swoft 框架运行分析(一) | 流动 +

      Swoft 框架运行分析(一)

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。

      以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。

      刚开始看的时候,感觉自己像个原始人,完全看不懂。

      官方文档没有介绍Swoft的实现,网上的一些文章跟当前版本代码已经不一致了。

      自己花了一周时间,终于梳理清楚了,看完更觉得自己是个原始人了。

      使用的框架组件版本为:

      swoft-2.0.5
      +swoft-component-2.0.5
      +

      这里以Swoft启动http server为例。

      php bin/swoft http:start
      +

      执行上述命令,启动http server。

      这里执行的是bin/swoft文件。

      #!/usr/bin/env php
      +<?php declare(strict_types=1);
      +
      +// Bootstrap
      +require_once __DIR__ . '/bootstrap.php';
      +
      +Swoole\Coroutine::set([
      +    'max_coroutine' => 300000,
      +]);
      +
      +// Run application
      +(new \App\Application())->run();
      +

      这里引入bootstrap.php文件,引入composer自动加载文件。

      <?php
      +// Composer autoload
      +require_once dirname(__DIR__) . '/vendor/autoload.php';
      +

      然后执行Swoft\App\Application类下的run方法。

      <?php declare(strict_types=1);
      +
      +namespace App;
      +
      +use Swoft\SwoftApplication;
      +use function date_default_timezone_set;
      +
      +/**
      + * Class Application
      + *
      + * @since 2.0
      + */
      +class Application extends SwoftApplication
      +{
      +    protected function beforeInit(): void
      +    {
      +        parent::beforeInit();
      +
      +        date_default_timezone_set('Asia/Shanghai');
      +    }
      +}
      +

      这里继承了Swoft\SwoftApplication类,这里只粘贴了部分代码。

      /**
      + * Swoft application
      + *
      + * @since 2.0
      + */
      +class SwoftApplication implements SwoftInterface, ApplicationInterface
      +{
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $config
      +     */
      +    public function __construct(array $config = [])
      +    {
      +        // Check runtime env
      +        SwoftHelper::checkRuntime();
      +
      +        // Storage as global static property.
      +        Swoft::$app = $this;
      +
      +        // Before init
      +        $this->beforeInit();
      +
      +        // Init console logger
      +        $this->initCLogger();
      +
      +        // Can setting properties by array
      +        if ($config) {
      +            ObjectHelper::init($this, $config);
      +        }
      +
      +        // Init application
      +        $this->init();
      +
      +        CLog::info('Project path is <info>%s</info>', $this->basePath);
      +
      +        // After init
      +        $this->afterInit();
      +    }
      +
      +    protected function init(): void
      +    {
      +        // Init system path aliases
      +        $this->findBasePath();
      +        $this->setSystemAlias();
      +
      +        $processors = $this->processors();
      +
      +        $this->processor = new ApplicationProcessor($this);
      +        $this->processor->addFirstProcessor(...$processors);
      +    }
      +
      +    /**
      +     * Run application
      +     */
      +    public function run(): void
      +    {
      +        if (!$this->beforeRun()) {
      +            return;
      +        }
      +
      +        $this->processor->handle();
      +    }
      +
      +    /**
      +     * @return ProcessorInterface[]
      +     */
      +    protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      __construct方法里检查运行环境,初始化日志组件,然后调用了init方法。

      init方法里声明了processor对象。

      processors方法定义了Swoft框架的6个Processor对象。

      run方法里直接调用processor对象的handler方法。

      <?php
      +
      +namespace Swoft\Processor;
      +
      +use Swoft\Stdlib\Helper\ArrayHelper;
      +use function get_class;
      +
      +/**
      + * Application processor
      + * @since 2.0
      + */
      +class ApplicationProcessor extends Processor
      +{
      +    /**
      +     * @var ProcessorInterface[]
      +     */
      +    private $processors = [];
      +
      +    /**
      +     * Handle application processors
      +     */
      +    public function handle(): bool
      +    {
      +        $disabled = $this->application->getDisabledProcessors();
      +
      +        foreach ($this->processors as $processor) {
      +            $class = get_class($processor);
      +
      +            // If is disabled, skip handle.
      +            if (isset($disabled[$class])) {
      +                continue;
      +            }
      +
      +            $processor->handle();
      +        }
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add first processor
      +     *
      +     * @param Processor[] $processor
      +     * @return bool
      +     */
      +    public function addFirstProcessor(Processor ...$processor): bool
      +    {
      +        array_unshift($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add last processor
      +     *
      +     * @param Processor[] $processor
      +     *
      +     * @return bool
      +     */
      +    public function addLastProcessor(Processor ...$processor): bool
      +    {
      +        array_push($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add processors
      +     *
      +     * @param int         $index
      +     * @param Processor[] $processors
      +     *
      +     * @return bool
      +     */
      +    public function addProcessor(int $index, Processor  ...$processors): bool
      +    {
      +        ArrayHelper::insert($this->processors, $index, ...$processors);
      +
      +        return true;
      +    }
      +}
      +

      addFirstProcessor方法把process对象赋值给$this->processors

      handle方法遍历processors对象,循环执行handle方法。

      Swoft的核心逻辑都是靠上面定义的6个Processor模块实现的,接下来一个一个分析。

      💬评论
      \ No newline at end of file diff --git a/posts/the-cors-issue-with-302-redirect/302.png b/posts/the-cors-issue-with-302-redirect/302.png new file mode 100644 index 000000000..1151e9b98 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302.png differ diff --git a/posts/the-cors-issue-with-302-redirect/302_hu12483722819857021138.png b/posts/the-cors-issue-with-302-redirect/302_hu12483722819857021138.png new file mode 100644 index 000000000..d509cfcea Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302_hu12483722819857021138.png differ diff --git a/posts/the-cors-issue-with-302-redirect/302_hu13914265401418104620.webp b/posts/the-cors-issue-with-302-redirect/302_hu13914265401418104620.webp new file mode 100644 index 000000000..2bbce588a Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302_hu13914265401418104620.webp differ diff --git a/posts/the-cors-issue-with-302-redirect/302_return_origin_header.png b/posts/the-cors-issue-with-302-redirect/302_return_origin_header.png new file mode 100644 index 000000000..4d742f7dd Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302_return_origin_header.png differ diff --git a/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu13484935516549152346.webp b/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu13484935516549152346.webp new file mode 100644 index 000000000..7f13c4965 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu13484935516549152346.webp differ diff --git a/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu316862039115063263.png b/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu316862039115063263.png new file mode 100644 index 000000000..88d2eb465 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/302_return_origin_header_hu316862039115063263.png differ diff --git a/posts/the-cors-issue-with-302-redirect/index.html b/posts/the-cors-issue-with-302-redirect/index.html new file mode 100644 index 000000000..627d62dd0 --- /dev/null +++ b/posts/the-cors-issue-with-302-redirect/index.html @@ -0,0 +1,109 @@ +302跳转的跨域问题(CORS) | 流动 +

      302跳转的跨域问题(CORS)

      302跳转的跨域问题

      场景一:302不返回跨域头

      请求

      GET /302 HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      HTTP/1.1 200 OK
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      Access to fetch at 'https://liudon.xyz/302' from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      302

      场景二:302跳转返回跨域头

      请求

      GET /302_return_origin_header HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      HTTP/1.1 200 OK
      +Access-Control-Allow-Origin: https://www.baidu.com
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      Access to fetch at 'https://liudon.org/' (redirected from 'https://liudon.xyz/302_return_origin_header') from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      302 return origin header

      注意,这里302跳转请求没有报错,是跳转后的连接报了跨域错误。

      Location请求

      GET / HTTP/1.1
      +Host: liudon.org
      +Origin: null
      +Referer: https://www.baidu.com/
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      location

      302返回了跨域头,所以浏览器请求了Location地址。

      但为什么两次请求header头里的Origin字段值不一致呢?第二次Location请求为什么Origin字段值是null?

      第一次:
      +Origin: https://www.baidu.com
      +
      +第二次
      +Origin: null
      +

      经过一番搜索,终于找到了一些资料。

      The Origin header value may be null in a number of cases, including (non-exhaustively):

      Origins whose scheme is not one of http, https, ftp, ws, wss, or gopher (including blob, file and data). +Cross-origin images and media data, including that in , and elements. +Documents created programmatically using createDocument(), generated from a data: URL, or that do not have a creator browsing context. +Redirects across origins. +iframes with a sandbox attribute that doesn’t contain the value allow-same-origin. +Responses that are network errors. +Referrer-Policy set to no-referrer for non-cors request modes (e.g. simple form posts).

      出自 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description

      A request request has a redirect-tainted origin if these steps return true:

      Let lastURL be null.

      For each url of request’s URL list:

      If lastURL is null, then set lastURL to url and continue.

      If url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true.

      Set lastURL to url. +Return false. +Serializing a request origin, given a request request, is to run these steps:

      If request has a redirect-tainted origin, then return “null”.

      Return request’s origin, serialized.

      出自 https://fetch.spec.whatwg.org/#concept-request-tainted-origin

      简单说就是如果302跳转的域与上一次请求域不同的话,就会将Origin设置为null

      💬评论
      \ No newline at end of file diff --git a/posts/the-cors-issue-with-302-redirect/location.png b/posts/the-cors-issue-with-302-redirect/location.png new file mode 100644 index 000000000..ce6441456 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/location.png differ diff --git a/posts/the-cors-issue-with-302-redirect/location_hu16254348894024931263.png b/posts/the-cors-issue-with-302-redirect/location_hu16254348894024931263.png new file mode 100644 index 000000000..eebc9a813 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/location_hu16254348894024931263.png differ diff --git a/posts/the-cors-issue-with-302-redirect/location_hu9849758582764196190.webp b/posts/the-cors-issue-with-302-redirect/location_hu9849758582764196190.webp new file mode 100644 index 000000000..9e6275eb1 Binary files /dev/null and b/posts/the-cors-issue-with-302-redirect/location_hu9849758582764196190.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359.jpg new file mode 100644 index 000000000..6bce5d2b2 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu13773308122635967302.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu13773308122635967302.webp new file mode 100644 index 000000000..00e283863 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu13773308122635967302.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu16350445115033178451.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu16350445115033178451.jpg new file mode 100644 index 000000000..ba23b79f5 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210359_hu16350445115033178451.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413.jpg new file mode 100644 index 000000000..2711f643a Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu16297431278291949475.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu16297431278291949475.webp new file mode 100644 index 000000000..f3899727a Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu16297431278291949475.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu169497635061842828.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu169497635061842828.jpg new file mode 100644 index 000000000..6b40d069d Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210413_hu169497635061842828.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418.jpg new file mode 100644 index 000000000..fcf741115 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu12878139162045151367.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu12878139162045151367.webp new file mode 100644 index 000000000..1639b478b Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu12878139162045151367.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu14322683064006180744.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu14322683064006180744.jpg new file mode 100644 index 000000000..2f1ac2f5a Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210418_hu14322683064006180744.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424.jpg new file mode 100644 index 000000000..4c8b2ce5b Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu18175370897587808874.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu18175370897587808874.webp new file mode 100644 index 000000000..7deee87de Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu18175370897587808874.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu8408143810863175133.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu8408143810863175133.jpg new file mode 100644 index 000000000..c94e93e65 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210424_hu8408143810863175133.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431.jpg new file mode 100644 index 000000000..3cc40755f Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu13586914886567271236.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu13586914886567271236.jpg new file mode 100644 index 000000000..96de07212 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu13586914886567271236.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu17801422576362920580.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu17801422576362920580.webp new file mode 100644 index 000000000..aa6998c43 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210431_hu17801422576362920580.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436.jpg new file mode 100644 index 000000000..c59baa30a Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu12697348293945292906.webp b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu12697348293945292906.webp new file mode 100644 index 000000000..d37ed0ae4 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu12697348293945292906.webp differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu4075605898751983642.jpg b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu4075605898751983642.jpg new file mode 100644 index 000000000..58364dd89 Binary files /dev/null and b/posts/the-first-chinese-new-year-after-the-covid-19/20230216210436_hu4075605898751983642.jpg differ diff --git a/posts/the-first-chinese-new-year-after-the-covid-19/index.html b/posts/the-first-chinese-new-year-after-the-covid-19/index.html new file mode 100644 index 000000000..6f2cf050e --- /dev/null +++ b/posts/the-first-chinese-new-year-after-the-covid-19/index.html @@ -0,0 +1,21 @@ +新冠疫情后的第一个春节 | 流动 +

      新冠疫情后的第一个春节

      下面的内容是由chatGPT润色生成的。

      AI太强大了 😂

      当我还是个孩子的时候,在看春节晚会时,总会有节目介绍那些不能回家过年的人。

      但我从未想过,等我长大后,我也会成为其中的一员。

      由于疫情的影响,我已经连续三年不能回家过年了。

      每次我告诉父母我无法回家,他们总是表现得非常平静,但我不知道他们挂了电话后的心情会如何。

      直到今年,我们全家都经历了一次感染,但这也使我们有了机会在这个特殊的春节回家过年。

      提前请了假,带娃体验下老家的生活。

      回家

      回家啦。

      赶集

      赶大集。

      烧火。

      放烟花

      放烟花。

      抓鸟

      抓鸟。

      放孔明灯

      放孔明灯。

      蹭饭

      邻居家蹭饭。

      💬评论
      \ No newline at end of file diff --git a/posts/the-first-post/index.html b/posts/the-first-post/index.html new file mode 100644 index 000000000..01830e317 --- /dev/null +++ b/posts/the-first-post/index.html @@ -0,0 +1,14 @@ +2019,新开始 | 流动 +

      2019,新开始

      从2011年开始写博客,博客程序从WordPress换成Typecho。 +早就有想法换成静态博客,一直没时间搞。

      2019年了,新年新气象,用hugo + github pages搞了个新博客。

      具体部署过程参考文章: +利用Travis CI和Hugo將Blog自動部署到Github Pages

      这篇文章就是通过这种方式来更新的,感觉很是神奇。 +再也不用关注服务器性能这些东西了,只需要专心写字就好了。

      接下来只需要搞定自定义域名了,域名还在认证中,无法做解析。

      自定义域名也搞定了,以后就可以正式切到新博客了。

      老博客只做备份了,不再更新了。

      💬评论
      \ No newline at end of file diff --git a/posts/the-trip-of-qingdao/caishui.png b/posts/the-trip-of-qingdao/caishui.png new file mode 100644 index 000000000..c0c836537 Binary files /dev/null and b/posts/the-trip-of-qingdao/caishui.png differ diff --git a/posts/the-trip-of-qingdao/caishui_hu4959903825995376216.png b/posts/the-trip-of-qingdao/caishui_hu4959903825995376216.png new file mode 100644 index 000000000..8a8d79cdb Binary files /dev/null and b/posts/the-trip-of-qingdao/caishui_hu4959903825995376216.png differ diff --git a/posts/the-trip-of-qingdao/caishui_hu5514519093367087150.webp b/posts/the-trip-of-qingdao/caishui_hu5514519093367087150.webp new file mode 100644 index 000000000..ff25d1751 Binary files /dev/null and b/posts/the-trip-of-qingdao/caishui_hu5514519093367087150.webp differ diff --git a/posts/the-trip-of-qingdao/chiwan.png b/posts/the-trip-of-qingdao/chiwan.png new file mode 100644 index 000000000..7a2b94e76 Binary files /dev/null and b/posts/the-trip-of-qingdao/chiwan.png differ diff --git a/posts/the-trip-of-qingdao/chiwan_hu12309867523409753068.webp b/posts/the-trip-of-qingdao/chiwan_hu12309867523409753068.webp new file mode 100644 index 000000000..2b3043c0e Binary files /dev/null and b/posts/the-trip-of-qingdao/chiwan_hu12309867523409753068.webp differ diff --git a/posts/the-trip-of-qingdao/chiwan_hu16201326701270414051.png b/posts/the-trip-of-qingdao/chiwan_hu16201326701270414051.png new file mode 100644 index 000000000..4a746d962 Binary files /dev/null and b/posts/the-trip-of-qingdao/chiwan_hu16201326701270414051.png differ diff --git a/posts/the-trip-of-qingdao/disanhaishuiyuchang.png b/posts/the-trip-of-qingdao/disanhaishuiyuchang.png new file mode 100644 index 000000000..6da31a0dc Binary files /dev/null and b/posts/the-trip-of-qingdao/disanhaishuiyuchang.png differ diff --git a/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu5258025648427076937.png b/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu5258025648427076937.png new file mode 100644 index 000000000..00f757970 Binary files /dev/null and b/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu5258025648427076937.png differ diff --git a/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu8456013446709072296.webp b/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu8456013446709072296.webp new file mode 100644 index 000000000..4bbe071a1 Binary files /dev/null and b/posts/the-trip-of-qingdao/disanhaishuiyuchang_hu8456013446709072296.webp differ diff --git a/posts/the-trip-of-qingdao/index.html b/posts/the-trip-of-qingdao/index.html new file mode 100644 index 000000000..bacf181c6 --- /dev/null +++ b/posts/the-trip-of-qingdao/index.html @@ -0,0 +1,26 @@ +一次简短的青岛之行 | 流动 +

      一次简短的青岛之行

      刚放暑假的时候,就答应了娃带她去一趟青岛。

      8月份要回老家,所以定在了7月中下旬出发。

      车票/酒店都订好了,结果来了个台风格美。

      出发前一周一直在查天气,就怕去了一直下雨。

      看了台风的预测路径,感觉可能能赶在台风来之前的空档,硬着头皮出发吧。

      7月26日乘坐高铁G203,中午12点左右到达青岛。

      老天很给面子,是个晴天,还有点晒。

      出行计划

      本来的计划路线:

      第一天:
      +
      +中午到达青岛 -> 天主教堂 -> 栈桥 -> 八大关 -> 第二海水浴场
      +
      +第二天:
      +
      +海底世界/极地海洋馆 -> 石老人海水浴场 -> 五四广场/奥帆中心夜景
      +
      +第三天:
      +
      +酒店休息返京
      +

      按这个路线,定了两个酒店,一个在栈桥附近,一个在海洋馆附近。

      到了青岛后,先去酒店放行李,然后打车去吃饭。

      青岛的第一顿饭,我们选了吃海鲜,事前查了些攻略,选择了栈桥附近的燕欣饭馆。

      燕欣饭馆

      中午很饿,上来就吃,忘记拍照了,只有吃完后的照片了。

      一扫而光

      油焖大虾相当不错,海肠捞饭非常好吃,韭菜很鲜。

      3个人,一共花了240元,非常推荐的一家店。

      吃完饭,溜达到天主教堂打卡。

      天主教堂

      然后是栈桥,人非常多,中午非常晒。

      栈桥

      于是回酒店稍作休息,决定打车直奔第二海水浴场玩水。

      踩水的感觉太好玩了,娃从一开始的有点害怕,到后面追着水玩。

      踩水

      玩完打车去的美团推荐的双合园,地方很小,需要等位。

      吃下来,感觉不如第一顿好吃,菜品一般,不太推荐。

      吃完已经9点了,错过了夜景时间,直接回酒店休息了。

      第二天起床,发现外面下雨了,风很大,最终没逃过台风的影响。

      昨天路过海洋馆,看了外面排队的人巨多,决定不去室内这种海洋馆了。

      先去了银鱼巷溜达一圈,没啥看的,中午在1907青岛老味道吃的午饭,非常不推荐的一家店。

      吃完饭打车到奥帆中心,想着坐船玩一圈,到了发现因为风大停运了。

      五四广场打卡

      打卡完,直奔第三海水浴场玩水。

      到了发现因为风大不让下水了,只能在沙滩上玩沙子了。

      第三海水浴场

      和娃一起抓了几只小螃蟹,虽然天气不好,娃玩的还是很开心。

      赶海

      实在不想吃海鲜了,晚饭在酒店附近吃了个米村拌饭,发现旁边有家类似北京的老年厨房,非常便宜,菜品也不错,第二天早晨在这里解决了。

      天气不好,晚上在酒店看电视了,点了个麦当劳夜宵,结果娃没吃多少,全我吃了,给我撑的。

      第三天天晴了,但是风还是大。

      决定到第二海水浴场看看运气,到了之后还是不让下水,在沙滩上玩了会沙子。

      时间差不多,回酒店办退房。

      然后步行到火车站,上车回京。

      青岛之行就此结束了,尽管行程很短,天气不太好,但一家人在一起就很开心,唯一的教训就是晚上夜宵不要吃的太多。 😂

      💬评论
      \ No newline at end of file diff --git a/posts/the-trip-of-qingdao/jiaotang.png b/posts/the-trip-of-qingdao/jiaotang.png new file mode 100644 index 000000000..69610ff04 Binary files /dev/null and b/posts/the-trip-of-qingdao/jiaotang.png differ diff --git a/posts/the-trip-of-qingdao/jiaotang_hu8489432499697884342.png b/posts/the-trip-of-qingdao/jiaotang_hu8489432499697884342.png new file mode 100644 index 000000000..c9131cb89 Binary files /dev/null and b/posts/the-trip-of-qingdao/jiaotang_hu8489432499697884342.png differ diff --git a/posts/the-trip-of-qingdao/jiaotang_hu9435358433890495332.webp b/posts/the-trip-of-qingdao/jiaotang_hu9435358433890495332.webp new file mode 100644 index 000000000..6172e6e05 Binary files /dev/null and b/posts/the-trip-of-qingdao/jiaotang_hu9435358433890495332.webp differ diff --git a/posts/the-trip-of-qingdao/pangxie.png b/posts/the-trip-of-qingdao/pangxie.png new file mode 100644 index 000000000..0bfe028d2 Binary files /dev/null and b/posts/the-trip-of-qingdao/pangxie.png differ diff --git a/posts/the-trip-of-qingdao/pangxie_hu14237613133558251072.webp b/posts/the-trip-of-qingdao/pangxie_hu14237613133558251072.webp new file mode 100644 index 000000000..5ea870907 Binary files /dev/null and b/posts/the-trip-of-qingdao/pangxie_hu14237613133558251072.webp differ diff --git a/posts/the-trip-of-qingdao/pangxie_hu3787972242369278222.png b/posts/the-trip-of-qingdao/pangxie_hu3787972242369278222.png new file mode 100644 index 000000000..17fbd8009 Binary files /dev/null and b/posts/the-trip-of-qingdao/pangxie_hu3787972242369278222.png differ diff --git a/posts/the-trip-of-qingdao/route.png b/posts/the-trip-of-qingdao/route.png new file mode 100644 index 000000000..f1c6c3572 Binary files /dev/null and b/posts/the-trip-of-qingdao/route.png differ diff --git a/posts/the-trip-of-qingdao/route_hu12609839345040660021.webp b/posts/the-trip-of-qingdao/route_hu12609839345040660021.webp new file mode 100644 index 000000000..ebc843950 Binary files /dev/null and b/posts/the-trip-of-qingdao/route_hu12609839345040660021.webp differ diff --git a/posts/the-trip-of-qingdao/route_hu13506007279713045843.png b/posts/the-trip-of-qingdao/route_hu13506007279713045843.png new file mode 100644 index 000000000..426c94355 Binary files /dev/null and b/posts/the-trip-of-qingdao/route_hu13506007279713045843.png differ diff --git a/posts/the-trip-of-qingdao/wusiguangchang.png b/posts/the-trip-of-qingdao/wusiguangchang.png new file mode 100644 index 000000000..baafba47e Binary files /dev/null and b/posts/the-trip-of-qingdao/wusiguangchang.png differ diff --git a/posts/the-trip-of-qingdao/wusiguangchang_hu10967046976155803611.png b/posts/the-trip-of-qingdao/wusiguangchang_hu10967046976155803611.png new file mode 100644 index 000000000..836bc0bd8 Binary files /dev/null and b/posts/the-trip-of-qingdao/wusiguangchang_hu10967046976155803611.png differ diff --git a/posts/the-trip-of-qingdao/wusiguangchang_hu5127428064637612307.webp b/posts/the-trip-of-qingdao/wusiguangchang_hu5127428064637612307.webp new file mode 100644 index 000000000..3284d4a9e Binary files /dev/null and b/posts/the-trip-of-qingdao/wusiguangchang_hu5127428064637612307.webp differ diff --git a/posts/the-trip-of-qingdao/yanxin.png b/posts/the-trip-of-qingdao/yanxin.png new file mode 100644 index 000000000..7c33b6172 Binary files /dev/null and b/posts/the-trip-of-qingdao/yanxin.png differ diff --git a/posts/the-trip-of-qingdao/yanxin_hu1097675480764191817.png b/posts/the-trip-of-qingdao/yanxin_hu1097675480764191817.png new file mode 100644 index 000000000..3ed7e67b6 Binary files /dev/null and b/posts/the-trip-of-qingdao/yanxin_hu1097675480764191817.png differ diff --git a/posts/the-trip-of-qingdao/yanxin_hu9902128616467469194.webp b/posts/the-trip-of-qingdao/yanxin_hu9902128616467469194.webp new file mode 100644 index 000000000..593c71469 Binary files /dev/null and b/posts/the-trip-of-qingdao/yanxin_hu9902128616467469194.webp differ diff --git a/posts/the-trip-of-qingdao/zhanqiao.png b/posts/the-trip-of-qingdao/zhanqiao.png new file mode 100644 index 000000000..ed0b07df1 Binary files /dev/null and b/posts/the-trip-of-qingdao/zhanqiao.png differ diff --git a/posts/the-trip-of-qingdao/zhanqiao_hu14243345525381039576.png b/posts/the-trip-of-qingdao/zhanqiao_hu14243345525381039576.png new file mode 100644 index 000000000..905a30e6a Binary files /dev/null and b/posts/the-trip-of-qingdao/zhanqiao_hu14243345525381039576.png differ diff --git a/posts/the-trip-of-qingdao/zhanqiao_hu9847400616821491205.webp b/posts/the-trip-of-qingdao/zhanqiao_hu9847400616821491205.webp new file mode 100644 index 000000000..8a259031a Binary files /dev/null and b/posts/the-trip-of-qingdao/zhanqiao_hu9847400616821491205.webp differ diff --git a/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv.jpg b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv.jpg new file mode 100644 index 000000000..a896893ca Binary files /dev/null and b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv.jpg differ diff --git a/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu18275729127856576344.jpg b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu18275729127856576344.jpg new file mode 100644 index 000000000..9b38aa0fa Binary files /dev/null and b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu18275729127856576344.jpg differ diff --git a/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu541995141045692674.webp b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu541995141045692674.webp new file mode 100644 index 000000000..5278bb13e Binary files /dev/null and b/posts/there-are-no-packages-available-for-installation/63c9befaly1fz7anr0smxj20fg0famzv_hu541995141045692674.webp differ diff --git a/posts/there-are-no-packages-available-for-installation/index.html b/posts/there-are-no-packages-available-for-installation/index.html new file mode 100644 index 000000000..435362096 --- /dev/null +++ b/posts/there-are-no-packages-available-for-installation/index.html @@ -0,0 +1,16 @@ +解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错 | 流动 +

      解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错

      今天安装hugofy的包时,一直遇到"There Are No Packages Available for Installation"的错误。 +按网上的教程,配置host,配置代理都不起作用。

      本机确定是可以访问https://packagecontrol.io/channel_v3.json这个地址的。

      然后按教程把这文件放到本地,配置channel指向本地这个文件,然后提示json解析失败。 +然后检查这个文件,发现文件好像不全。然后换到其他机器curl这个地址,发现下载下来的文件确实不全,不是合法的json内容。

      下载文件内容截图

      又搜索一番后,找到一个case。

      Package Control: There are no packages available for installation/Server Error

      原来是官方的文件下载出问题了,可以先按上面链接里的方法修改,验证可行。

      Meanwhile, you can add
      +"channels": [ "https://erhan.in/channel_v3.json" ],
      +to Preferences > Package Settings > Package Control > Settings - User file.
      +
      +This is the latest snapshot of the original JSON file from web.archive.org.
      +
      💬评论
      \ No newline at end of file diff --git a/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025.png b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025.png new file mode 100644 index 000000000..99b0545bd Binary files /dev/null and b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025.png differ diff --git a/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu10871856927439122702.webp b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu10871856927439122702.webp new file mode 100644 index 000000000..ccc223b91 Binary files /dev/null and b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu10871856927439122702.webp differ diff --git a/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu7733977588862373690.png b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu7733977588862373690.png new file mode 100644 index 000000000..23329e8ed Binary files /dev/null and b/posts/using-github-actions-to-schedule-weibo-scraping/20231007132025_hu7733977588862373690.png differ diff --git a/posts/using-github-actions-to-schedule-weibo-scraping/index.html b/posts/using-github-actions-to-schedule-weibo-scraping/index.html new file mode 100644 index 000000000..8a2844940 --- /dev/null +++ b/posts/using-github-actions-to-schedule-weibo-scraping/index.html @@ -0,0 +1,70 @@ +利用Github Actions定时抓取微博 | 流动 +

      利用Github Actions定时抓取微博

      背景

      在微博上关注了一些用户,比如tk教主月风

      但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。

      实现方案

      整体思路:利用Github ActionsScheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。

      1. 新建仓库,比如weibo_archive

      2. 添加抓取脚本,完整代码

        这里用到微博两个接口:

        // 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断
        +https://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom&type=uid&value=$uid&containerid=107603$uid
        +
        +// 根据微博id,抓取微博完整的内容
        +https://m.weibo.cn/statuses/extend?id=$id
        +
      3. 添加环境变量。

        • 进入个人设置->Developer Settings->Personal access tokens->Tokens (classic),创建新的Token,记下对应的值。

        • 进入第一步创建仓库的配置页,点击Secrets and variables下的Actions

          切到Secret目录,创建新的Secret变量,名称为TOKEN,值为前一步记录的值;切到Variables目录,创建新的Variables变量,名称为WEIBO_UIDS,值为你需要抓取的微博用户id,多个用户的话以|分割。

      4. 添加定时任务,完整yaml文件如下。

        # This is a basic workflow to help you get started with Actions
        +
        +name: CI
        +
        +# Controls when the workflow will run
        +on:
        +# Triggers the workflow on push or pull request events but only for the "main" branch
        +push:
        +    branches: [ "main" ]
        +pull_request:
        +    branches: [ "main" ]
        +
        +# Allows you to run this workflow manually from the Actions tab
        +workflow_dispatch:
        +schedule:
        +    - cron: '*/5 * * * *'
        +
        +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
        +jobs:
        +# This workflow contains a single job called "build"
        +build:
        +    # The type of runner that the job will run on
        +    runs-on: ubuntu-latest
        +
        +    permissions:      
        +    contents: write
        +
        +    # Steps represent a sequence of tasks that will be executed as part of the job
        +    steps:
        +    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
        +    - uses: actions/checkout@v3
        +
        +    # Runs a single command using the runners shell
        +    - name: archive weibo
        +        run: |
        +        chmod +x ./weibo_archive.sh
        +        ./weibo_archive.sh
        +        shell: bash
        +        env:
        +        weibo_uids: ${{ vars.weibo_uids }}
        +
        +    # Runs a set of commands using the runners shell
        +    - name: Commit changes
        +        uses: EndBug/add-and-commit@v9
        +        env:
        +        github_token: ${{ secrets.TOKEN }}
        +        add: .
        +

      效果

      抓取后的内容,会按用户id分别保存到不同文件。

      效果

      不过这个方案有一个唯一的缺点,Github Actions定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。

      Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.

      💬评论
      \ No newline at end of file diff --git "a/posts/\344\270\200\346\254\241\346\203\212\345\277\203\345\212\250\351\255\204\347\232\204mysql\346\233\264\346\226\260\346\223\215\344\275\234/index.html" "b/posts/\344\270\200\346\254\241\346\203\212\345\277\203\345\212\250\351\255\204\347\232\204mysql\346\233\264\346\226\260\346\223\215\344\275\234/index.html" new file mode 100644 index 000000000..aa3656e74 --- /dev/null +++ "b/posts/\344\270\200\346\254\241\346\203\212\345\277\203\345\212\250\351\255\204\347\232\204mysql\346\233\264\346\226\260\346\223\215\344\275\234/index.html" @@ -0,0 +1,135 @@ +一次惊心动魄的Mysql更新操作 | 流动 +

      一次惊心动魄的Mysql更新操作

      问题描述

      # 表结构
      +MySQL > desc user_packages;
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| Field          | Type                | Null | Key | Default             | Extra          |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| up_id          | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
      +| start_date     | date                | NO   |     | NULL                |                |
      +| end_date       | date                | NO   |     | NULL                |                |
      +| up_created     | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
      +| up_updated     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +5 rows in set (0.00 sec)
      +
      +MySQL > select * from user_packages limit 5;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 2018-06-30 |
      +|   186 | 2018-04-01 | 2018-06-30 |
      +|   187 | 2018-04-01 | 2018-06-30 |
      +|   188 | 2018-04-01 | 2018-06-30 |
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +5 rows in set (0.00 sec)
      +

      操作过程

      需要更新某条记录的end_date字段,执行sql如下:

      MySQL > update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +Query OK, 1 row affected, 1 warning (0.00 sec)
      +Rows matched: 1  Changed: 1  Warnings: 1
      +

      执行完,发现sql写错了!!!!

      正确的sql应该是:

      update user_packages set end_date = '2020-06-06' where up_id = 189 limit 1;
      +

      误把where写成了and,还好指定了limit = 1,只操作了一条记录。

      回滚

      回滚的前提,要先找到更新的那条记录。

      up_id为表的主键,更新前表里已经有这条记录了,主键不能重复,感觉语句应该没有执行成功。

      MySQL > select * from user_packages where up_id = 189;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      执行查询语句,表里确实也只有这一条up_id=189的记录。

      感觉这个update语句应该没执行成功,但是没执行成功应该报错的呀。

      这个时候把希望放到了语句结果里的Warnings: 1,是不是没执行成功呢。

      因为紧接着又执行了其他语句,所以也无法通过show warnings查看具体的错误信息了。

      那么这条语句到底执行成功了吗?如果执行成功,那么修改的是哪条记录呢?

      这里通过一番查找,终于定位到了记录。

      AND分隔符,在mysql语句里优先级最高。

      update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +
      +等效为
      +
      +update user_packages set end_date = ('2020-06-06' and up_id = 189) limit 1;
      +
      +即
      +
      +update user_packages set end_date = 0 limit 1;
      +

      因为end_date字段为date类型,所以写入表后的记录为0000-00-00

      MySQL > select * from user_packages where end_date = '0000-00-00';
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 0000-00-00 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      好在这次只更新了一条记录,否则后果无法想象。

      切记不要在现网直接操作DB。

      相关资料:

      一个我认为是bug的UPDATE语句

      💬评论
      \ No newline at end of file diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46.jpeg" new file mode 100644 index 000000000..1c543bcf2 Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu1031091904409366834.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu1031091904409366834.jpeg" new file mode 100644 index 000000000..26fc4a6e5 Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu1031091904409366834.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu14337547491232210022.webp" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu14337547491232210022.webp" new file mode 100644 index 000000000..700dbeac1 Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG46_hu14337547491232210022.webp" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47.jpeg" new file mode 100644 index 000000000..5763452ca Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu1076955541246434091.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu1076955541246434091.jpeg" new file mode 100644 index 000000000..ae0ba90b2 Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu1076955541246434091.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu8797114940761609245.webp" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu8797114940761609245.webp" new file mode 100644 index 000000000..90e3c28ff Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG47_hu8797114940761609245.webp" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48.jpeg" new file mode 100644 index 000000000..c39eb9e4b Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu1172572908314036841.webp" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu1172572908314036841.webp" new file mode 100644 index 000000000..5c7e07f4f Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu1172572908314036841.webp" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu14949915704554245906.jpeg" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu14949915704554245906.jpeg" new file mode 100644 index 000000000..7ffe4be31 Binary files /dev/null and "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/WechatIMG48_hu14949915704554245906.jpeg" differ diff --git "a/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/index.html" "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/index.html" new file mode 100644 index 000000000..9bb51f5b0 --- /dev/null +++ "b/posts/\344\272\214\345\210\267\347\231\276\346\234\233\345\261\261/index.html" @@ -0,0 +1,18 @@ +二刷百望山 | 流动 +

      二刷百望山

      又是周末,娃约了小伙伴一起爬山。

      百望山,二刷走起。

      约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。

      出门晚了,还打不到车,快10点才到。

      小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。

      继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。

      这次来,山上明显绿了,花开的更多了,人比上次少多了。

      下山后,又一起吃了个饭,自助吃的有点撑。

      今天天气真好,视野相当不错,就是太晒了。

      俯瞰北京

      不知名的树

      山顶

      💬评论
      \ No newline at end of file diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog.png" new file mode 100644 index 000000000..e1552175b Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu14843736785607511932.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu14843736785607511932.webp" new file mode 100644 index 000000000..d80977a2d Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu14843736785607511932.webp" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu16306086537580768154.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu16306086537580768154.png" new file mode 100644 index 000000000..8e8733c37 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/blog_hu16306086537580768154.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn.png" new file mode 100644 index 000000000..5f78a32ae Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu2441418303555843155.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu2441418303555843155.png" new file mode 100644 index 000000000..0f99549db Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu2441418303555843155.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu7682288896726837721.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu7682288896726837721.webp" new file mode 100644 index 000000000..e278c53ac Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/cdn_hu7682288896726837721.webp" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns.png" new file mode 100644 index 000000000..cd2f29b48 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu17868764939609469181.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu17868764939609469181.webp" new file mode 100644 index 000000000..8a0be7533 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu17868764939609469181.webp" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu5955705786880707343.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu5955705786880707343.png" new file mode 100644 index 000000000..83dd1551f Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/dns_hu5955705786880707343.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/index.html" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/index.html" new file mode 100644 index 000000000..e9d917d55 --- /dev/null +++ "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/index.html" @@ -0,0 +1,110 @@ +加速Cloudflare访问 | 流动 +

      加速Cloudflare访问

      背景

      博客架构

      这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。

      国内访问情况

      众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。

      今天,我们就来讲一下,如何实现国内海外双线路博客访问。

      大体思路

      海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。
      +
      +Cloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。
      +
      +国内CDN添加域名后,也会提供一个cname域名B。
      +
      +使用国内dns解析服务,配置cname双线路解析。
      +

      具体操作

      1. Cloudflare Page添加新域名解析

        这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。

        Cloudflare Page添加域名

      2. 配置国内CDN

        我用的腾讯云,其他服务商也是可以的。

        添加CDN域名

        加速域名:填写博客对外访问的域名
        +回源地址和Host:填写第一步新加的域名
        +

        添加成功后,会有一个cname地址,这里是国内线路解析要用到的。

      3. DNS解析调整

        Cloudflare不支持双线路配置,国内服务商支持,我这里用的是腾讯云。

        首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到Cloudflare的邮件,不需要理会。

        liudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:
        +
        +sandals.dnspod.net
        +heron.dnspod.net
        +[not set]
        +[not set]
        +[not set]
        +
        +此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。
        +

        然后添加解析,默认走国内CDN,境外走Cloudflare Page

        DNS双线路解析

      额外的问题

      为了加速Google Analytics,使用Cloudflare Worker进行了反代,具体见加速Google Analytics

      更改NS后,导致海外访问无法触发Cloudflare Worker了,导致没有博客统计数据了。

      经过一番搜索后,发现Cloudflare Page有类似的Function功能,只需要在网站根目录下新建functions目录,添加对应文件即可。

      这里以Hugo静态博客举例说明:

      在根目录的static目录下,新建functions目录,新建analytics目录,添加post.js文件。

      这个analytics/post.js是为了对应原有Worker的访问地址analytics/post,可自行修改。

      post.js文件代码如下:

      export async function onRequest(context) {
      +    try {
      +        return await postHandler(context);
      +    } catch(e) {
      +        return new Response(`${e.message}\n${e.stack}`, { status: 500 }); 
      +    }
      +}
      +
      +async function postHandler(context) {
      +    const GA_DOMAIN = 'google-analytics.com';
      +    const GA_COLLECT_PATH = 'g\/collect';
      +    const COLLECT_PATH = 'analytics/post';
      +    const DOMAIN = '这里填你博客的域名';
      +
      +    const url = context.request.url;
      +    const cf_ip = context.request.headers.get('CF-Connecting-IP');
      +    const cf_country = context.request.cf.country;
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`)
      +    const newReq = await readRequest(context.request, ga_url);
      +    context.waitUntil(fetch(newReq));
      +
      +    return new Response(null, {
      +        status: 204,
      +        statusText: 'No Content',
      +      });
      +}
      +
      +async function readRequest(request, url) {
      +    const { _, headers } = request;
      +    const nq = {
      +      method: request.method,
      +      headers: {
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: request.body,
      +    };
      +    return new Request(url, nq);
      +}
      +

      优化效果

      优化后的访问

      有了国内CDN的加持,平均耗时优化到1s左右了。

      💬评论
      \ No newline at end of file diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page.png" new file mode 100644 index 000000000..ea8901f84 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu279659283536568196.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu279659283536568196.png" new file mode 100644 index 000000000..94f0ecea3 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu279659283536568196.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu5001045409527403165.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu5001045409527403165.webp" new file mode 100644 index 000000000..0f46be35a Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/page_hu5001045409527403165.webp" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result.png" new file mode 100644 index 000000000..dcafd095a Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu16478955910687504533.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu16478955910687504533.png" new file mode 100644 index 000000000..ad3e9b6a0 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu16478955910687504533.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu5247482790724463766.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu5247482790724463766.webp" new file mode 100644 index 000000000..1e56fa248 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/result_hu5247482790724463766.webp" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow.png" new file mode 100644 index 000000000..900b1de8f Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10803827016559902140.png" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10803827016559902140.png" new file mode 100644 index 000000000..cb33dcdfa Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10803827016559902140.png" differ diff --git "a/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10826307968286423618.webp" "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10826307968286423618.webp" new file mode 100644 index 000000000..9b4535857 Binary files /dev/null and "b/posts/\345\212\240\351\200\237cloudflare\350\256\277\351\227\256/slow_hu10826307968286423618.webp" differ diff --git "a/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171559362.jpg" "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171559362.jpg" new file mode 100644 index 000000000..68a7c6703 Binary files /dev/null and "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171559362.jpg" differ diff --git "a/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171603897.jpg" "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171603897.jpg" new file mode 100644 index 000000000..7aea688fd Binary files /dev/null and "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171603897.jpg" differ diff --git "a/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171612269.jpg" "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171612269.jpg" new file mode 100644 index 000000000..547e543af Binary files /dev/null and "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/202309171612269.jpg" differ diff --git "a/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/index.html" "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/index.html" new file mode 100644 index 000000000..dac226312 --- /dev/null +++ "b/posts/\345\214\227\345\244\247\345\217\243\350\205\224\347\211\231\345\221\250\345\210\256\346\262\273\350\256\260\345\275\225/index.html" @@ -0,0 +1,23 @@ +北大口腔牙周刮治记录 | 流动 +

      北大口腔牙周刮治记录

      病情

      上次洗完牙后,还是不时有出血的情况。

      前段时间更是出现牙龈劈开一块肉,特别容易塞东西的情况。

      于是,又跑到医院来看牙了。

      医生检查后,说是牙周病,需要牙周刮治,要约到8月份了。

      回来查了下,牙周刮治还是挺痛苦的,想着找个专业医院看看再。

      挂号

      于是开始北大口腔挂号,中间挂了一次总院的普通号,但是网上好多说是规培生操作,又取消了。

      找了农行的代挂号服务,北大口腔只能微信预约,他们也没渠道,只能自己挂了。

      总院实在是挂不到,于是转战第一门诊部的号。

      功夫不负有心人,7月5日总算挂上了牙周科李菲医生的号。

      治疗

      7月7日,第一次看医生,刮治是没的跑了。

      做了牙齿探针,每个牙都要测一下,略疼。

      开了治疗计划,刮治分两次,每次半口牙,总费用3000元左右。

      第一次只洗牙,确实是专业,洗牙不到半个小时搞定。

      最关键的,洗完后我刷牙确实很少再出血了。

      8月3日,第一次刮治,只做了右半边。

      医生说我这算是轻的,刮治比洗牙会疼一些,可以打麻药也可以不打。

      行吧,那咱就不打麻药,来吧。

      医生开始操作,感觉和洗牙是一样的,也是拿喷水的机器在牙上弄来弄去。

      但是比洗牙更深一些,感觉是直接到牙龈下面了,确实要更疼一些。

      洗完后,再拿一个铁丝一样的东西在牙上使劲刮来刮去。

      自我感觉,还是前面洗牙更痛苦一些,后面刮来刮去没什么感觉。

      铁丝一样的工具,第二次刮治拍的

      半个小时搞定,本来我以为我这得一俩小时,医生说那得是非常严重的,你这属于轻的。

      9月2日,第二次刮治,做了左半边。

      左边牙龈问题更压重些,这次痛感更强烈一些。

      同样也是半个小时搞定,1个月后再来复诊。

      费用

      洗牙:480元,单子找不到了,我记得是这个数。
      +
      +第一次刮治:1286元
      +
      +第二次刮治:1176元
      +

      总费用2942元,基本都走了医保。

      💬评论
      \ No newline at end of file diff --git "a/posts/\345\215\201\344\270\200\345\271\264\347\232\204\347\255\211\345\276\205\347\273\210\344\272\216\346\213\277\345\210\260\344\272\206liudon.com\345\237\237\345\220\215/index.html" "b/posts/\345\215\201\344\270\200\345\271\264\347\232\204\347\255\211\345\276\205\347\273\210\344\272\216\346\213\277\345\210\260\344\272\206liudon.com\345\237\237\345\220\215/index.html" new file mode 100644 index 000000000..947c90003 --- /dev/null +++ "b/posts/\345\215\201\344\270\200\345\271\264\347\232\204\347\255\211\345\276\205\347\273\210\344\272\216\346\213\277\345\210\260\344\272\206liudon.com\345\237\237\345\220\215/index.html" @@ -0,0 +1,15 @@ +十一年的等待,终于拿到了liudon.com域名 | 流动 +

      十一年的等待,终于拿到了liudon.com域名

      关于部分,有写域名的来历。

      当时liudon.com已经被注册,所以只好注册了liudon.org

      2011年注册的liudon.org,最早用wordpress搭建了博客。

      这是当时的第一篇文章,Hello world

      从2011年,到2019年,中间断断续续的在更新着,博客程序也从wordpress转到了typecho,再后来开始流行静态页博客,又转到了hugo下。

      在这中间,一直想要拿到liudon.com域名。

      看网站内容,应该是个德国乐队的网站,这里能看到网站最后的信息

      3月10日的时候,偶然看到群里有在说域名,于是又搜索了一番,发现域名上了阿里云域名拍卖,购买的时候提示卖家已下家。

      到国外域名商上查了一下,godaddy提供代购,name提示为高级域名,可以购买。

      archive.org上的信息,2021年11月的时候服务不可用了,不知道发生了什么。

      机不可失,时不再来,立马下单。

      接下来就是漫长的等待了,系统显示需要5-10个工作日才能完成交易。

      同时我发现他们服务条款写着,高级域名不支持退款,whois信息一直不变,担心打水漂。

      期间有一天晚上11点联系客户,说是已经在加快处理。

      过了10天后,交易还没完成,有点放弃了。

      3月24日晚上,再次查whois,发现信息有变,登录name,发现订单已完成。

      现在这个域名终于属于我了。

      liudon.org 老站,备份,不再更新
      +liudon.com -> github pages
      +liudon.xyz -> netfily -> ipfs
      +
      💬评论
      \ No newline at end of file diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/index.html" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/index.html" new file mode 100644 index 000000000..78454d3bf --- /dev/null +++ "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/index.html" @@ -0,0 +1,43 @@ +博客架构说明 | 流动 +

      博客架构说明

      在拿到liudon.com域名前,手中已有两个域名:

      • liudon.org
      • liudon.xyz

      两套域名说明

      liudon.org已经不再更新,仅作归档使用。 +liudon.xyz当时是静态博客流行,尝鲜使用。

      拿到liudon.com域名后,怎么部署博客成了个问题。

      因为github pages只能绑定一个自定义域名,当然可以通过创建另外一个项目,实现两套域名,但是同一个博客两个项目总感觉不太优雅。

      经过一番资料查找,终于有了下面这套方案。

      博客构建流程

      通过github actionsnetlify 部署了两套自动化部署方案:

      • github actions部署到github pages,绑定自定义域名liudon.com
      • netlify部署到ipfs,通过cloudfare ipfs gateway解析到ipfs资源,绑定自定义域名liudon.xyz

      这里要说明一下,ipfs目前访问延迟较大,这里仅作尝鲜使用。

      hugoconfig.toml定义了网站域名,这里为了区分两套域名,在netlify部署时,对配置文件做了修改,保证两套域名访问各自页面,具体可参考github文件内容

      💬评论
      \ No newline at end of file diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213.png" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213.png" new file mode 100644 index 000000000..1ee473c8a Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213.png" differ diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu2580045572028185263.png" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu2580045572028185263.png" new file mode 100644 index 000000000..1e23903ed Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu2580045572028185263.png" differ diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu5498963674612948386.webp" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu5498963674612948386.webp" new file mode 100644 index 000000000..4b76df2a5 Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\204\345\273\272\346\265\201\347\250\213_hu5498963674612948386.webp" differ diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204.png" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204.png" new file mode 100644 index 000000000..c915dc8e7 Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204.png" differ diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu10108568587195088134.webp" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu10108568587195088134.webp" new file mode 100644 index 000000000..71024908f Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu10108568587195088134.webp" differ diff --git "a/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu6151068063620748445.png" "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu6151068063620748445.png" new file mode 100644 index 000000000..0c4ac1c81 Binary files /dev/null and "b/posts/\345\215\232\345\256\242\346\236\266\346\236\204\350\257\264\346\230\216/\345\215\232\345\256\242\346\236\266\346\236\204_hu6151068063620748445.png" differ diff --git "a/posts/\345\267\245\351\223\266\344\272\232\346\264\262\347\275\221\351\223\266\345\257\206\347\240\201\351\207\215\347\275\256/index.html" "b/posts/\345\267\245\351\223\266\344\272\232\346\264\262\347\275\221\351\223\266\345\257\206\347\240\201\351\207\215\347\275\256/index.html" new file mode 100644 index 000000000..cfd0b906c --- /dev/null +++ "b/posts/\345\267\245\351\223\266\344\272\232\346\264\262\347\275\221\351\223\266\345\257\206\347\240\201\351\207\215\347\275\256/index.html" @@ -0,0 +1,15 @@ +工银亚洲网银密码重置 | 流动 +

      工银亚洲网银密码重置

      18年的时候办了张工银亚洲的银行卡,好几年没有用过了。

      今年想起来了,发现网银登不上了,密码忘了。

      最悲剧的是,试了超过10次,账户冻结了。

      打95588咨询了一下,说是可以内地见证方式修改密码,只需要带上港澳通行证和身份证即可。

      1月27日,东城出入境办理港澳通行证签注,排队半个小时左右,自助机操作。

      顺路跑到附近可以见证开户的工行,说需要香港那边配合,人周末不上班,只能工作日。

      电话西二旗支行,说是可以早上早点来办理,要不过年人多,没时间处理。

      1月30日,早上直奔银行,赶在开门前到,第一个办理。

      工作人员会指导你填写资料,填完后会给你两个信封,里面是重置后的网银密码。

      1月31日下午,95588打电话,我以为是广告没接。

      第二天早上反应过来,在路上的时候又打过来了。

      香港那边直接电话,跟你确认身份信息后,下午就收到工行发来的已更新资料邮件。

      其实这里密码就重置完了,因为我同时办理了更新通信地址,我以为是只更新了地址。

      至此,整个流程就办完了,还是非常方便的。

      另外,发现招银亚洲也没法登陆了,U盾过期了,又跑了一趟招行,重新办了个U盾。

      好了,这下两个银行都可以正常使用了。

      💬评论
      \ No newline at end of file diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40.jpeg" new file mode 100644 index 000000000..be58a5124 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu14606740751763050506.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu14606740751763050506.jpeg" new file mode 100644 index 000000000..95fb9ab76 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu14606740751763050506.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu2651074913868480496.webp" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu2651074913868480496.webp" new file mode 100644 index 000000000..de1ba8369 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG40_hu2651074913868480496.webp" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41.jpeg" new file mode 100644 index 000000000..6e5e4c537 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu1751030110810078032.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu1751030110810078032.jpeg" new file mode 100644 index 000000000..321ab5a03 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu1751030110810078032.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu18101397133616820375.webp" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu18101397133616820375.webp" new file mode 100644 index 000000000..671385d41 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG41_hu18101397133616820375.webp" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42.jpeg" new file mode 100644 index 000000000..626701876 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu14106534961347157697.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu14106534961347157697.jpeg" new file mode 100644 index 000000000..593add53d Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu14106534961347157697.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu18186140578598785649.webp" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu18186140578598785649.webp" new file mode 100644 index 000000000..ae08f1b29 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG42_hu18186140578598785649.webp" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43.jpeg" new file mode 100644 index 000000000..c88e33b08 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu15577018806750825269.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu15577018806750825269.jpeg" new file mode 100644 index 000000000..06d0a243c Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu15577018806750825269.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu8329383171754737012.webp" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu8329383171754737012.webp" new file mode 100644 index 000000000..a8f8a4ab6 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG43_hu8329383171754737012.webp" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44.jpeg" new file mode 100644 index 000000000..f94ffe4d3 Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu10264368041309766573.webp" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu10264368041309766573.webp" new file mode 100644 index 000000000..b4d27c37c Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu10264368041309766573.webp" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu7961631850814716237.jpeg" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu7961631850814716237.jpeg" new file mode 100644 index 000000000..9db90e80e Binary files /dev/null and "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/WechatIMG44_hu7961631850814716237.jpeg" differ diff --git "a/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/index.html" "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/index.html" new file mode 100644 index 000000000..f27cc4d5d --- /dev/null +++ "b/posts/\345\270\246\345\250\203\346\270\270\351\242\220\345\222\214\345\233\255/index.html" @@ -0,0 +1,12 @@ +带娃游颐和园 | 流动 +

      带娃游颐和园

      上周的北海之行,本来是想划船的,可惜人太多没有划成,答应娃这周末带她去划船。

      周六7点准时起床,得早点去省得人多排队。

      8点半吃完饭洗漱完出发,特意带娃坐了一趟双层公交车 —— 二楼第一排观光区。

      路上不堵,40多分钟到站。

      进园先去码头排队,人不太多,等了有十几分钟,终于轮到了。

      总算开上了船,不过天气不太好,灰蒙蒙的。

      终于坐上了船

      水上荡了1个小时,下船向佛香阁进发。

      佛香阁

      一路向上,终于登顶,园内风景一览无余。

      下来后,又去逛了十七孔桥,逛了下湖心小岛。

      回家又特意坐的双层公交,第一排观光区,哈哈。

      一天下来1w多步,娃估计得更多,到家直接累摊。

      这是鸳鸯吧

      佛香阁

      十七孔桥

      💬评论
      \ No newline at end of file diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210.jpg" new file mode 100644 index 000000000..b011ae825 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14151708002844463235.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14151708002844463235.webp" new file mode 100644 index 000000000..fcfd26db0 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14151708002844463235.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14313312843032992940.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14313312843032992940.jpg" new file mode 100644 index 000000000..d541e5309 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu14313312843032992940.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu17649131178520809984.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu17649131178520809984.jpg" new file mode 100644 index 000000000..13b80cb34 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu17649131178520809984.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu3006740006649713065.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu3006740006649713065.webp" new file mode 100644 index 000000000..a78d94fda Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu3006740006649713065.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu4934023691421517793.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu4934023691421517793.webp" new file mode 100644 index 000000000..c80d2ca25 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu4934023691421517793.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu8434099771930637415.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu8434099771930637415.jpg" new file mode 100644 index 000000000..cbb16ea9b Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348210_hu8434099771930637415.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324.jpg" new file mode 100644 index 000000000..82cc57a07 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu13205494613761534942.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu13205494613761534942.jpg" new file mode 100644 index 000000000..d9cbfe293 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu13205494613761534942.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1397528316934953203.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1397528316934953203.webp" new file mode 100644 index 000000000..b73ca2895 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1397528316934953203.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu14037396544662102675.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu14037396544662102675.jpg" new file mode 100644 index 000000000..e60e7b50c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu14037396544662102675.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu15618538421707335487.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu15618538421707335487.webp" new file mode 100644 index 000000000..4df5f728f Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu15618538421707335487.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1593567249556602312.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1593567249556602312.jpg" new file mode 100644 index 000000000..958c645d9 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu1593567249556602312.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu6336645708170525226.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu6336645708170525226.webp" new file mode 100644 index 000000000..19a462a5e Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092348324_hu6336645708170525226.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469.jpg" new file mode 100644 index 000000000..96c05874c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu12606325031551251496.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu12606325031551251496.jpg" new file mode 100644 index 000000000..48461156c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu12606325031551251496.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu14776936020413754579.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu14776936020413754579.webp" new file mode 100644 index 000000000..bbf4616c6 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu14776936020413754579.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2597285684635397051.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2597285684635397051.jpg" new file mode 100644 index 000000000..20a7cc0ed Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2597285684635397051.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2666303332358173500.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2666303332358173500.webp" new file mode 100644 index 000000000..355d8dd15 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu2666303332358173500.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu5328217669090157384.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu5328217669090157384.webp" new file mode 100644 index 000000000..9f9f74ab7 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu5328217669090157384.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu7852407319406279843.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu7852407319406279843.jpg" new file mode 100644 index 000000000..117c21691 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349469_hu7852407319406279843.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674.jpg" new file mode 100644 index 000000000..cce0fec29 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu17315677465720342703.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu17315677465720342703.jpg" new file mode 100644 index 000000000..43da1f8fa Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu17315677465720342703.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu469253816186341926.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu469253816186341926.webp" new file mode 100644 index 000000000..5c56f6b76 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu469253816186341926.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu6878470649797184229.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu6878470649797184229.webp" new file mode 100644 index 000000000..86e330fde Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu6878470649797184229.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu7226568527224028820.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu7226568527224028820.jpg" new file mode 100644 index 000000000..8abef22bd Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu7226568527224028820.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9220987119735129066.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9220987119735129066.webp" new file mode 100644 index 000000000..47465307c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9220987119735129066.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9767249176170565089.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9767249176170565089.jpg" new file mode 100644 index 000000000..241dbb190 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092349674_hu9767249176170565089.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308.jpg" new file mode 100644 index 000000000..720e204ab Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu145005599180908817.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu145005599180908817.webp" new file mode 100644 index 000000000..3b15c46ef Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu145005599180908817.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu15308385156392274254.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu15308385156392274254.jpg" new file mode 100644 index 000000000..79ff75c95 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu15308385156392274254.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu509790328523226346.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu509790328523226346.jpg" new file mode 100644 index 000000000..62ac74d39 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu509790328523226346.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu5541540640269891982.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu5541540640269891982.webp" new file mode 100644 index 000000000..34ad608da Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu5541540640269891982.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu7037997050479197269.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu7037997050479197269.webp" new file mode 100644 index 000000000..645a3f3ff Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu7037997050479197269.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu731076964394887047.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu731076964394887047.jpg" new file mode 100644 index 000000000..b7230b173 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350308_hu731076964394887047.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619.jpg" new file mode 100644 index 000000000..c94f6645d Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu16617486809879436799.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu16617486809879436799.webp" new file mode 100644 index 000000000..d9400f9ba Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu16617486809879436799.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu17710070191127292268.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu17710070191127292268.jpg" new file mode 100644 index 000000000..832bd84df Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu17710070191127292268.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu2433505532983287836.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu2433505532983287836.jpg" new file mode 100644 index 000000000..6270fedfc Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu2433505532983287836.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu5729448113798199425.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu5729448113798199425.jpg" new file mode 100644 index 000000000..1f46f49c0 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu5729448113798199425.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7036418364564234182.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7036418364564234182.webp" new file mode 100644 index 000000000..600cce3e4 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7036418364564234182.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7432699745329030812.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7432699745329030812.webp" new file mode 100644 index 000000000..d0e7b9be4 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092350619_hu7432699745329030812.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506.jpg" new file mode 100644 index 000000000..7b28e8a33 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu10069595325473335433.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu10069595325473335433.jpg" new file mode 100644 index 000000000..2a6df0a57 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu10069595325473335433.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu1434482351744164694.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu1434482351744164694.jpg" new file mode 100644 index 000000000..dfd79c969 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu1434482351744164694.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu14462402126953649247.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu14462402126953649247.webp" new file mode 100644 index 000000000..5158472d0 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu14462402126953649247.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu15516876150097055546.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu15516876150097055546.jpg" new file mode 100644 index 000000000..00e1e0681 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu15516876150097055546.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu7872454228474058102.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu7872454228474058102.webp" new file mode 100644 index 000000000..3410d9e62 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu7872454228474058102.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu8321561583722494546.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu8321561583722494546.webp" new file mode 100644 index 000000000..edd766843 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092351506_hu8321561583722494546.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581.jpg" new file mode 100644 index 000000000..66e4bf8ba Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu11215193266338815767.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu11215193266338815767.webp" new file mode 100644 index 000000000..08c15c581 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu11215193266338815767.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu14014938150088315224.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu14014938150088315224.webp" new file mode 100644 index 000000000..cf71f9516 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu14014938150088315224.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu15866005919592955040.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu15866005919592955040.jpg" new file mode 100644 index 000000000..b21948228 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu15866005919592955040.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu17800906480283189709.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu17800906480283189709.jpg" new file mode 100644 index 000000000..305faf862 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu17800906480283189709.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu2921174638698463898.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu2921174638698463898.jpg" new file mode 100644 index 000000000..462550fec Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu2921174638698463898.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu987640561682868003.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu987640561682868003.webp" new file mode 100644 index 000000000..597732564 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352581_hu987640561682868003.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743.jpg" new file mode 100644 index 000000000..460bde87c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu17327165547691344568.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu17327165547691344568.webp" new file mode 100644 index 000000000..ae89a880b Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu17327165547691344568.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2435452082129100458.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2435452082129100458.jpg" new file mode 100644 index 000000000..5db1879dd Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2435452082129100458.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2638088975046242217.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2638088975046242217.jpg" new file mode 100644 index 000000000..cfd70dd87 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2638088975046242217.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2710780007104389899.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2710780007104389899.webp" new file mode 100644 index 000000000..9c96a7875 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu2710780007104389899.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu302272236195815730.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu302272236195815730.jpg" new file mode 100644 index 000000000..69f5077b7 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu302272236195815730.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu9410619633719661015.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu9410619633719661015.webp" new file mode 100644 index 000000000..d4bd243c2 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092352743_hu9410619633719661015.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811.jpg" new file mode 100644 index 000000000..0373695b6 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu10546012771229261709.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu10546012771229261709.jpg" new file mode 100644 index 000000000..a2fefe90c Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu10546012771229261709.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu17015132040012874964.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu17015132040012874964.webp" new file mode 100644 index 000000000..20240eebc Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu17015132040012874964.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu3134778518951265071.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu3134778518951265071.webp" new file mode 100644 index 000000000..8ed7ece06 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu3134778518951265071.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu4982306534026316998.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu4982306534026316998.jpg" new file mode 100644 index 000000000..5b47c4a2d Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu4982306534026316998.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu5664365770069011367.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu5664365770069011367.jpg" new file mode 100644 index 000000000..32a46b387 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu5664365770069011367.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu6307723781029201127.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu6307723781029201127.webp" new file mode 100644 index 000000000..7b522af53 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309092353811_hu6307723781029201127.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461.jpg" new file mode 100644 index 000000000..083246095 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11645023921160621769.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11645023921160621769.jpg" new file mode 100644 index 000000000..4be048374 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11645023921160621769.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11836963035739125659.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11836963035739125659.jpg" new file mode 100644 index 000000000..3c66bc61d Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu11836963035739125659.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu14597285372421505784.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu14597285372421505784.webp" new file mode 100644 index 000000000..416a92c3b Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu14597285372421505784.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu15478602443917385830.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu15478602443917385830.webp" new file mode 100644 index 000000000..a14cbaa83 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu15478602443917385830.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu2123523744818986231.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu2123523744818986231.jpg" new file mode 100644 index 000000000..b572b1dfa Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu2123523744818986231.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu4403035001115536031.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu4403035001115536031.webp" new file mode 100644 index 000000000..f3e7f5822 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101306461_hu4403035001115536031.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174.jpg" new file mode 100644 index 000000000..d4c270498 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu12686240451327229605.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu12686240451327229605.webp" new file mode 100644 index 000000000..de3411a99 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu12686240451327229605.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu2603988060458277865.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu2603988060458277865.jpg" new file mode 100644 index 000000000..fe4214a36 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu2603988060458277865.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu5439968662717085529.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu5439968662717085529.jpg" new file mode 100644 index 000000000..dd23668fc Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu5439968662717085529.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7146746786070588117.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7146746786070588117.webp" new file mode 100644 index 000000000..0743e0721 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7146746786070588117.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7654230755973072684.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7654230755973072684.webp" new file mode 100644 index 000000000..cbb970097 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu7654230755973072684.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu9366285813139017495.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu9366285813139017495.jpg" new file mode 100644 index 000000000..02426c306 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101307174_hu9366285813139017495.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503.jpg" new file mode 100644 index 000000000..e24aa87db Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu11063552035295180075.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu11063552035295180075.webp" new file mode 100644 index 000000000..80ba68846 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu11063552035295180075.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu122183408365254010.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu122183408365254010.webp" new file mode 100644 index 000000000..31105485a Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu122183408365254010.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu17445641280591727019.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu17445641280591727019.jpg" new file mode 100644 index 000000000..923f712b6 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu17445641280591727019.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu3072706142550964296.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu3072706142550964296.jpg" new file mode 100644 index 000000000..6d26177f6 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu3072706142550964296.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu7261077057420717472.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu7261077057420717472.webp" new file mode 100644 index 000000000..ee88c3588 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu7261077057420717472.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu8022107772889313497.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu8022107772889313497.jpg" new file mode 100644 index 000000000..23344866e Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101308503_hu8022107772889313497.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020.jpg" new file mode 100644 index 000000000..b41ac0cc4 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu10709794427116929503.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu10709794427116929503.jpg" new file mode 100644 index 000000000..4c98e92e4 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu10709794427116929503.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu12202480585815608712.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu12202480585815608712.webp" new file mode 100644 index 000000000..e2b06b3bd Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu12202480585815608712.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu16921044953458615157.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu16921044953458615157.webp" new file mode 100644 index 000000000..01d392497 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu16921044953458615157.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu6714625670964178848.webp" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu6714625670964178848.webp" new file mode 100644 index 000000000..bf4301b4b Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu6714625670964178848.webp" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8248244727297415348.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8248244727297415348.jpg" new file mode 100644 index 000000000..262f4dbaa Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8248244727297415348.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8469938580071057749.jpg" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8469938580071057749.jpg" new file mode 100644 index 000000000..307fdb444 Binary files /dev/null and "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/202309101309020_hu8469938580071057749.jpg" differ diff --git "a/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/index.html" "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/index.html" new file mode 100644 index 000000000..248cc7ffc --- /dev/null +++ "b/posts/\346\225\205\344\271\241\345\233\236\345\277\206\344\271\213\346\227\205/index.html" @@ -0,0 +1,15 @@ +故乡回忆之旅 | 流动 +

      故乡回忆之旅

      赶在8月底,趁着娃暑假的尾声,回了趟老家。

      老家有条俗语,“永福庄的街,三里长”。

      这天吃完午饭,临时起意,带媳妇溜溜大街,见识下我们的大街。

      小时候,整天在这条街上跑来跑去。

      我们那巷子基本很少是堵头的,你可以穿到任意一个巷子,最后还能回到大街。

      小时候的大街,感觉很宽很长。

      现在大街都垫高了,大街也变的窄了许多。

      大街上的人很少,好多店铺都关门了。

      走在大街上,小时候的一幕幕映在眼前。

      有的地方,已经拆了重盖;

      有的地方,已经人去楼空。

      爷爷待过的大队部
      以前的供销社

      老爷爷家
      姥爷以前的药铺
      姥姥家

      最早是肉食店,后来开过药铺
      里面以前是个学校

      巷口的大饭店
      一栋风格怪异的楼
      巷子

      刘家祖庙
      赵家祖庙

      赵家祖庙
      关帝庙

      💬评论
      \ No newline at end of file diff --git "a/posts/\346\225\264\347\220\206\344\270\213\345\215\232\345\256\242\347\232\204\344\270\200\344\272\233\350\260\203\346\225\264/index.html" "b/posts/\346\225\264\347\220\206\344\270\213\345\215\232\345\256\242\347\232\204\344\270\200\344\272\233\350\260\203\346\225\264/index.html" new file mode 100644 index 000000000..3c0061372 --- /dev/null +++ "b/posts/\346\225\264\347\220\206\344\270\213\345\215\232\345\256\242\347\232\204\344\270\200\344\272\233\350\260\203\346\225\264/index.html" @@ -0,0 +1,25 @@ +整理下博客的一些调整 | 流动 +

      整理下博客的一些调整

      新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。

      1. 更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。

        通过调整Github Actions命令解决:

        - name: Checkout repository
        +    uses: actions/checkout@v2
        +  - name: Checkout submodules
        +    run: git submodule update --init --recursive --remote
        +
      2. 修正404页面不生效的问题 +主题是自带了404.html文件的,但是部署后没有生成对应文件。

        修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。

        可参考文件代码

      3. 两个域名导致的页面权重问题 +发现有些页面liudon.xyz收录后,liudon.com就不再收录。

        为了规避这种收录问题,将liudon.xyz直接301到了liudon.com上。

      目前已调整完毕,观察后续收录情况。

      💬评论
      \ No newline at end of file diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\344\272\224\344\270\200\345\201\207\346\234\237/index.html" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\344\272\224\344\270\200\345\201\207\346\234\237/index.html" new file mode 100644 index 000000000..97392b4ab --- /dev/null +++ "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\344\272\224\344\270\200\345\201\207\346\234\237/index.html" @@ -0,0 +1,12 @@ +疫情下的五一假期 | 流动 +

      疫情下的五一假期

      五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。

      当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。

      说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。

      毕竟老话说的好,手中有粮,心里不慌。

      第二天出去做核酸,特意去了趟超市,想着再买点肉,结果超市也是各种采购,东西都没了,空手而归。

      工作上五一前有两个大版本的功能要发布,提前1周需求才提,节前这一周忙的要死,还赶上了疫情,好在赶在发布截止日总算发出去了。

      就五一当天带娃去了家旁边的公园遛了下,这次轮滑滑的不错,一直玩到晚上7点才回家。

      夏天到了,北京的杨絮、柳絮各种满天飞,出门真是受罪,其余时间都在家呆着了,偶尔下楼在小区玩会。

      这个假期基本上每天核酸,感觉以后要常态化核酸了。

      老家在假期里突然也疫情又起,县城整个封控了,不过老爸还是回到家了,过程稍微费了点劲。

      突然发现今年的疫情貌似就没消停过,去年五一假期的时候在老家烧烤,今年还不知道什么时候能回家。

      这疫情什么时候是个头啊……

      💬评论
      \ No newline at end of file diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x.png" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x.png" new file mode 100644 index 000000000..5dd4e1b5c Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x.png" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu11460346391088325381.webp" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu11460346391088325381.webp" new file mode 100644 index 000000000..eb7d26dad Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu11460346391088325381.webp" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu3740727864247060874.png" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu3740727864247060874.png" new file mode 100644 index 000000000..f11ea3bf0 Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520-192500@2x_hu3740727864247060874.png" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127.png" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127.png" new file mode 100644 index 000000000..76b00a144 Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127.png" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu10017315860905738046.png" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu10017315860905738046.png" new file mode 100644 index 000000000..4a5cf9eb1 Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu10017315860905738046.png" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu5158594672106522355.webp" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu5158594672106522355.webp" new file mode 100644 index 000000000..87393e9fa Binary files /dev/null and "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/20220520194127_hu5158594672106522355.webp" differ diff --git "a/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/index.html" "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/index.html" new file mode 100644 index 000000000..161ddde4f --- /dev/null +++ "b/posts/\347\226\253\346\203\205\344\270\213\347\232\204\347\224\237\346\264\273/index.html" @@ -0,0 +1,30 @@ +疫情下的生活 | 流动 +

      疫情下的生活

      20220520-192500@2x

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。

      昨天看新闻,基本没有社会面新增了,感觉要解封了。

      没想到今天直接被打脸,封控升级了。

      居家办公已经快两周了,也不知道这种日子还要多久。

      在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。

      媳妇帮我想了个办法,投屏到电视上。

      20220520194127

      居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。

      不知道这波还要多久。

      💬评论
      \ No newline at end of file diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic.jpg" new file mode 100644 index 000000000..3fbf95bc2 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu17019945019773543030.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu17019945019773543030.webp" new file mode 100644 index 000000000..1269e65df Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu17019945019773543030.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu8176014428739287306.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu8176014428739287306.jpg" new file mode 100644 index 000000000..095ae8a16 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/491650584526_.pic_hu8176014428739287306.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic.jpg" new file mode 100644 index 000000000..ead2ee7b7 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu15048774124751003355.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu15048774124751003355.webp" new file mode 100644 index 000000000..f16f1a7a9 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu15048774124751003355.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu16468172364107356489.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu16468172364107356489.jpg" new file mode 100644 index 000000000..0481580ed Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/541650584531_.pic_hu16468172364107356489.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic.jpg" new file mode 100644 index 000000000..7e76496e8 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu12516063157909667316.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu12516063157909667316.webp" new file mode 100644 index 000000000..12643cd86 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu12516063157909667316.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu18445591559889898138.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu18445591559889898138.jpg" new file mode 100644 index 000000000..7dba01778 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/611650757849_.pic_hu18445591559889898138.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic.jpg" new file mode 100644 index 000000000..99d46a456 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13500467035088147851.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13500467035088147851.jpg" new file mode 100644 index 000000000..bba5ccd87 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13500467035088147851.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13531142737361007931.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13531142737361007931.webp" new file mode 100644 index 000000000..f31cb0b05 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/621650757850_.pic_hu13531142737361007931.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic.jpg" new file mode 100644 index 000000000..c986824e6 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu14733579810193593304.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu14733579810193593304.webp" new file mode 100644 index 000000000..115ef531a Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu14733579810193593304.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu7214629230934141490.jpg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu7214629230934141490.jpg" new file mode 100644 index 000000000..d41e98f37 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/631650757850_.pic_hu7214629230934141490.jpg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55.jpeg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55.jpeg" new file mode 100644 index 000000000..5cc694d56 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55.jpeg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu15693980055455728109.jpeg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu15693980055455728109.jpeg" new file mode 100644 index 000000000..5bfceaf1f Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu15693980055455728109.jpeg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu1688270271605025052.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu1688270271605025052.webp" new file mode 100644 index 000000000..008f85a94 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG55_hu1688270271605025052.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64.jpeg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64.jpeg" new file mode 100644 index 000000000..1fa575edb Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64.jpeg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu10257655814848621050.jpeg" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu10257655814848621050.jpeg" new file mode 100644 index 000000000..3cc026ff7 Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu10257655814848621050.jpeg" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu8366303833935516476.webp" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu8366303833935516476.webp" new file mode 100644 index 000000000..9dd0cac7d Binary files /dev/null and "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/WechatIMG64_hu8366303833935516476.webp" differ diff --git "a/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/index.html" "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/index.html" new file mode 100644 index 000000000..f3d676132 --- /dev/null +++ "b/posts/\350\207\252\345\267\261\345\212\250\346\211\213\346\233\264\346\215\242thinkpad-x1\347\241\254\347\233\230/index.html" @@ -0,0 +1,30 @@ +自己动手,更换thinkpad x1硬盘 | 流动 +

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。

      491650584526_.pic

      多次重启也不行,感觉是硬盘挂了。

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      WechatIMG64

      一共就5个螺丝,打开后盖。

      621650757850_.pic

      可以看到电池旁边的螺丝,拧下取出电池。

      631650757850_.pic

      拧开硬盘上的固定螺丝,取出硬盘。

      541650584531_.pic

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      WechatIMG55

      我在京东买的三星970 EVO Plus这款,顺利换上。

      611650757849_.pic

      重装系统,搞定,省了一笔,好开心。

      💬评论
      \ No newline at end of file diff --git "a/posts/\350\242\253\351\232\224\347\246\273\347\232\204\344\270\200\345\221\250/index.html" "b/posts/\350\242\253\351\232\224\347\246\273\347\232\204\344\270\200\345\221\250/index.html" new file mode 100644 index 000000000..f4d615d3c --- /dev/null +++ "b/posts/\350\242\253\351\232\224\347\246\273\347\232\204\344\270\200\345\221\250/index.html" @@ -0,0 +1,12 @@ +被隔离的一周 | 流动 +

      被隔离的一周

      从没有想过疫情会离自己这么近,记录一下。

      周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。

      周二早上全员核酸阴性,继续到公司上班。

      晚上8点10分刚和同事上地铁,部门群里有人说我们楼有人确诊,过了10分钟,说是大楼管控了,不让出楼了。

      封在楼里的同学统一做核酸,结果出来后才能回家,好多人在公司过了夜。

      周三到公司集体核酸,做完继续居家办公,今天开始公安局、社区开始联系确认信息,公司要求所有人员居家办公,社区反馈需要居家隔离。

      头几天还说对门上封条了,没成想这次轮到自己了。

      到03/31日,隔离解除,健康宝恢复正常。

      第一次体验到健康宝显示弹窗,然后又显示居家隔离。

      第一次体验到被贴封条上门磁。

      第一次体验到鼻拭子,尤其解封前最后一次核酸,双鼻孔。

      💬评论
      \ No newline at end of file diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612.png" new file mode 100644 index 000000000..11eac7005 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu18419126202489954049.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu18419126202489954049.png" new file mode 100644 index 000000000..4844364f6 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu18419126202489954049.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu3469823060694782874.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu3469823060694782874.webp" new file mode 100644 index 000000000..f387f3337 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525202612_hu3469823060694782874.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053.png" new file mode 100644 index 000000000..27ecea2d4 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu18367448392582228077.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu18367448392582228077.png" new file mode 100644 index 000000000..e5f0410eb Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu18367448392582228077.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu531925169399976859.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu531925169399976859.webp" new file mode 100644 index 000000000..3463caf6b Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220525204053_hu531925169399976859.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411.png" new file mode 100644 index 000000000..5e5a65735 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2232900895793906885.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2232900895793906885.png" new file mode 100644 index 000000000..5a5e2f133 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2232900895793906885.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2325871098114948662.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2325871098114948662.webp" new file mode 100644 index 000000000..779d4022d Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202411_hu2325871098114948662.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458.png" new file mode 100644 index 000000000..3f50024fb Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu5824582462004341895.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu5824582462004341895.webp" new file mode 100644 index 000000000..82308d261 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu5824582462004341895.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu9444041697601932939.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu9444041697601932939.png" new file mode 100644 index 000000000..e0fda8e50 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202458_hu9444041697601932939.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545.png" new file mode 100644 index 000000000..1d8077004 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu2821024475350553251.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu2821024475350553251.webp" new file mode 100644 index 000000000..4bb509651 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu2821024475350553251.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu4974026833093093908.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu4974026833093093908.png" new file mode 100644 index 000000000..a6b824d17 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220527202545_hu4974026833093093908.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840.png" new file mode 100644 index 000000000..2ad1181ca Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu4201153904083183550.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu4201153904083183550.webp" new file mode 100644 index 000000000..11abd602d Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu4201153904083183550.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu6710284553326682317.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu6710284553326682317.png" new file mode 100644 index 000000000..7f60bfc92 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629215840_hu6710284553326682317.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638.png" new file mode 100644 index 000000000..dd818cc9f Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu10555322135686406693.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu10555322135686406693.webp" new file mode 100644 index 000000000..b4af2b7b5 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu10555322135686406693.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu8345186713163492718.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu8345186713163492718.png" new file mode 100644 index 000000000..ec360b183 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220629220638_hu8345186713163492718.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342.png" new file mode 100644 index 000000000..1d1a1e851 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3365036360746829129.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3365036360746829129.webp" new file mode 100644 index 000000000..a0a50991b Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3365036360746829129.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3904377888923366737.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3904377888923366737.png" new file mode 100644 index 000000000..c2e9bee83 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710084342_hu3904377888923366737.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749.png" new file mode 100644 index 000000000..9544ed17c Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu17039102873566797094.png" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu17039102873566797094.png" new file mode 100644 index 000000000..9c78afda7 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu17039102873566797094.png" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu9769184962070657899.webp" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu9769184962070657899.webp" new file mode 100644 index 000000000..c3dad8db4 Binary files /dev/null and "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/20220710085749_hu9769184962070657899.webp" differ diff --git "a/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/index.html" "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/index.html" new file mode 100644 index 000000000..3249c0731 --- /dev/null +++ "b/posts/\350\256\260\345\275\2252022\345\271\264\346\265\267\346\267\200\345\271\274\345\215\207\345\260\217/index.html" @@ -0,0 +1,39 @@ +记录2022年海淀幼升小 | 流动 +

      记录2022年海淀幼升小

      20220525202612

      18年的热点新闻,纳税千万孩子无法在北京上学。

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      提前在网上搜了一番资料,都是一些机构整理的信息。

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      1. 信息采集

      5月5日,采集系统开放。

      当天下午录入相关信息,提交网上审核。

      2. 网上审核

      信息提交后,就开始了漫长的审核时间。

      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      20220527202411

      3. 线下审核

      网上审核通过后,打印入学申请表,预约线下审核时间。

      20220527202458

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      后来交流一番后,发现是自己理解错了。

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      街道审核

      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • 工作证明还需要提供满足时间要求的社保缴费记录。

      4. 审核通过

      5月27日,审核通过后,打印信息采集表。

      20220527202545

      5. 学校登记

      6月1日对口学校发布入学登记通知书。

      按通知书准备资料,到登记时间去学校交资料。

      今年遇到疫情,改为线上邮件发送资料登记了。

      6. 填报志愿

      6月23日,海淀教育发文1911后填报志愿通知

      20220629215840

      第一志愿锁定,其他志愿自己选择填报。

      6月25日锁定,不允许再修改。

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      租房的不需要填报志愿,等待派位。

      7. 查看结果

      20220629220638

      6月29日15点,系统开放结果查询。

      第一志愿录取,一直担心的调剂没有发生。

      7月10日,收到教委短信,系统查询录取通知书。

      20220710085749

      20220710084342

      历时1个多月的幼升小总算落地了。

      💬评论
      \ No newline at end of file diff --git "a/posts/\350\256\260\347\254\254\344\272\214\346\254\241\346\264\227\347\211\231/index.html" "b/posts/\350\256\260\347\254\254\344\272\214\346\254\241\346\264\227\347\211\231/index.html" new file mode 100644 index 000000000..a055ff5e9 --- /dev/null +++ "b/posts/\350\256\260\347\254\254\344\272\214\346\254\241\346\264\227\347\211\231/index.html" @@ -0,0 +1,12 @@ +记第二次洗牙 | 流动 +

      记第二次洗牙

      最近刷牙的时候,牙龈总是出血。

      距离上一次洗牙,已经有好几年了,感觉又该去洗一下牙了。

      上次跟媳妇两个人,在小区外面的私人诊所洗的,俩人花了1000多块钱。

      这次决定去医院看看,提前挂了号。

      医生看过后,说得洗牙,不过当天只能先做洗牙前的检查,洗牙得预约。

      开单子抽血检查,完事回家。

      到了预约的日子,约的是4点,3点左右就到医院了。

      15:57轮到我,进去一共20分钟完事。

      我以为还有别的事项,结果医生说好了。

      我说我感觉牙里卡着个东西,医生又拿起装备给我洗了一遍。

      上次洗牙花了快1个小时,这也太快了。

      洗牙费用380元,可以走医保报销。

      以往都说医院洗牙贵,没成想外面私人的要更贵。

      💬评论
      \ No newline at end of file diff --git "a/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39.png" "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39.png" new file mode 100644 index 000000000..8f7cdf89c Binary files /dev/null and "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39.png" differ diff --git "a/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu13659472189117285021.png" "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu13659472189117285021.png" new file mode 100644 index 000000000..caaa7c1f1 Binary files /dev/null and "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu13659472189117285021.png" differ diff --git "a/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu5924788240665826760.webp" "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu5924788240665826760.webp" new file mode 100644 index 000000000..c36b9280d Binary files /dev/null and "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/IMG39_hu5924788240665826760.webp" differ diff --git "a/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/index.html" "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/index.html" new file mode 100644 index 000000000..7b62cb1d1 --- /dev/null +++ "b/posts/\351\232\276\345\276\227\347\232\204\346\270\205\346\230\216\345\201\207\346\234\237/index.html" @@ -0,0 +1,21 @@ +难得的清明假期 | 流动 +

      难得的清明假期

      前面有写到,被隔离了一周,好在赶在假期开始前解除了隔离。

      趁着这次难得的假期,外出放松一下。

      1. 爬百望山。

        娃是第一次爬山,百望山不高,适合带娃体验爬山,我也从13、14年之后没再爬过山了。

        进园后,选择大路往上爬,中间走了一段山间台阶路。

        一路走走停停,补充能量,估计有半个小时,我们就登顶了。

        山上风有点大,视野不错,可以直接看到互联网的核心公司。

      2. 游北海公园

        本来是想着去公园划船,结果到了之后发现排队的人太多了,于是放弃,改为游白塔。

        我们是从北门进的园,然后一路向西去码头打算划船,绕着园子继续往南走。

        一路走到了南门,心想这出去了还咋去白塔,难道要再走回去?

        问了门口保安,说是可以出去往东走,从南门再进去。

        进去后,要爬好几层楼梯才能上到白塔平台,一览园内风景。

        北海白塔

      希望疫情尽快散去,恢复到正常的生活。

      💬评论
      \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 000000000..0ed620e22 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: +Sitemap: https://liudon.com/sitemap.xml diff --git a/search/index.html b/search/index.html new file mode 100644 index 000000000..604898e5a --- /dev/null +++ b/search/index.html @@ -0,0 +1,5 @@ +Search | 流动 +
      \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..cc95d7aa2 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,374 @@ + + + + https://liudon.com/tags/github-pages/ + 2024-09-24T21:30:21+08:00 + + https://liudon.com/posts/github-pages-deployment-tutorial/ + 2024-09-24T21:30:21+08:00 + + https://liudon.com/posts/ + 2024-09-24T21:30:21+08:00 + + https://liudon.com/tags/ + 2024-09-24T21:30:21+08:00 + + https://liudon.com/ + 2024-09-24T21:30:21+08:00 + + https://liudon.com/posts/building-a-workout-page/ + 2024-09-22T16:57:38+08:00 + + https://liudon.com/tags/follow/ + 2024-09-17T00:53:38+08:00 + + https://liudon.com/posts/hi-follow/ + 2024-09-17T00:53:38+08:00 + + https://liudon.com/posts/mid-autumn-festival-climb/ + 2024-09-16T23:56:16+08:00 + + https://liudon.com/tags/%E7%88%AC%E5%B1%B1/ + 2024-09-16T23:56:16+08:00 + + https://liudon.com/tags/%E9%81%9B%E5%A8%83/ + 2024-09-16T23:56:16+08:00 + + https://liudon.com/tags/google-adsense/ + 2024-09-16T23:18:50+08:00 + + https://liudon.com/posts/my-google-adsense-approval-journey/ + 2024-09-16T23:18:50+08:00 + + https://liudon.com/tags/filebase/ + 2024-09-04T22:39:37+08:00 + + https://liudon.com/tags/github/ + 2024-09-04T22:39:37+08:00 + + https://liudon.com/tags/ipfs/ + 2024-09-04T22:39:37+08:00 + + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + 2024-09-04T22:39:37+08:00 + + https://liudon.com/posts/the-trip-of-qingdao/ + 2024-08-31T21:24:53+08:00 + + https://liudon.com/tags/%E6%97%85%E8%A1%8C/ + 2024-08-31T21:24:53+08:00 + + https://liudon.com/tags/go/ + 2024-06-14T20:41:20+08:00 + + https://liudon.com/tags/protoc/ + 2024-06-14T20:41:20+08:00 + + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + 2024-06-14T20:41:20+08:00 + + https://liudon.com/tags/cloudflare/ + 2024-05-22T21:20:20+08:00 + + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + 2024-05-22T21:20:20+08:00 + + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + 2024-05-17T20:13:57+08:00 + + https://liudon.com/tags/cors/ + 2024-05-17T20:13:57+08:00 + + https://liudon.com/tags/gorm/ + 2024-04-18T21:25:24+08:00 + + https://liudon.com/posts/gorm-supports-sqlcommenter/ + 2024-04-18T21:25:24+08:00 + + https://liudon.com/tags/sqlcommenter/ + 2024-04-18T21:25:24+08:00 + + https://liudon.com/posts/how-gorm-generates-sql/ + 2024-04-18T21:14:24+08:00 + + https://liudon.com/posts/%E5%B7%A5%E9%93%B6%E4%BA%9A%E6%B4%B2%E7%BD%91%E9%93%B6%E5%AF%86%E7%A0%81%E9%87%8D%E7%BD%AE/ + 2024-03-16T10:09:59+08:00 + + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + 2024-02-21T20:25:49+08:00 + + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/ + 2024-02-21T20:25:49+08:00 + + https://liudon.com/posts/review-2023/ + 2024-01-04T18:41:20+08:00 + + https://liudon.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + 2024-01-04T18:41:20+08:00 + + https://liudon.com/posts/2023%E5%B9%B412%E6%9C%88%E5%8C%97%E4%BA%AC%E6%9A%B4%E9%9B%AA%E8%AE%B0%E5%BD%95/ + 2023-12-16T10:15:33+08:00 + + https://liudon.com/tags/hugo/ + 2023-12-10T08:29:05+08:00 + + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + 2023-12-10T08:29:05+08:00 + + https://liudon.com/tags/google-analytics/ + 2023-12-02T09:25:49+08:00 + + https://liudon.com/posts/optimize-google-analytics/ + 2023-12-02T09:25:49+08:00 + + https://liudon.com/tags/google/ + 2023-10-27T19:32:24+08:00 + + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + 2023-10-27T19:32:24+08:00 + + https://liudon.com/tags/netlify/ + 2023-10-19T19:46:32+08:00 + + https://liudon.com/tags/twikoo/ + 2023-10-19T19:46:32+08:00 + + https://liudon.com/tags/vercel/ + 2023-10-19T19:46:32+08:00 + + https://liudon.com/posts/deploy-twikoo-on-netlify/ + 2023-10-19T19:46:32+08:00 + + https://liudon.com/tags/weibo/ + 2023-10-07T13:17:57+08:00 + + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + 2023-10-07T13:17:57+08:00 + + https://liudon.com/posts/%E5%8C%97%E5%A4%A7%E5%8F%A3%E8%85%94%E7%89%99%E5%91%A8%E5%88%AE%E6%B2%BB%E8%AE%B0%E5%BD%95/ + 2023-09-17T15:46:32+08:00 + + https://liudon.com/posts/%E6%95%85%E4%B9%A1%E5%9B%9E%E5%BF%86%E4%B9%8B%E6%97%85/ + 2023-09-09T23:19:03+08:00 + + https://liudon.com/tags/golang/ + 2023-08-17T09:20:50+08:00 + + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + 2023-08-17T09:20:50+08:00 + + https://liudon.com/posts/fix-hugo-json-feed/ + 2023-03-25T14:11:18+08:00 + + https://liudon.com/tags/%E4%B8%AA%E4%BA%BA%E6%8A%80%E8%83%BD/ + 2023-03-24T20:48:24+08:00 + + https://liudon.com/posts/my-journey-of-learning-to-drive/ + 2023-03-24T20:48:24+08:00 + + https://liudon.com/posts/deploy-blog-to-ipfs/ + 2023-02-21T19:46:58+08:00 + + https://liudon.com/posts/the-first-chinese-new-year-after-the-covid-19/ + 2023-02-16T20:56:38+08:00 + + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + 2023-01-16T14:43:38+08:00 + + https://liudon.com/tags/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B/ + 2023-01-16T14:43:38+08:00 + + https://liudon.com/posts/review-2022/ + 2023-01-12T07:21:20+08:00 + + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + 2022-08-26T23:25:57+08:00 + + https://liudon.com/tags/cls/ + 2022-08-24T12:37:22+08:00 + + https://liudon.com/tags/pagermod/ + 2022-08-24T12:37:22+08:00 + + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + 2022-08-24T12:37:22+08:00 + + https://liudon.com/posts/fix-blog-cls/ + 2022-08-20T07:27:22+08:00 + + https://liudon.com/tags/cloudflare-pages/ + 2022-07-29T23:16:01+08:00 + + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + 2022-07-29T23:16:01+08:00 + + https://liudon.com/posts/olympic-park-sunflower-tour/ + 2022-07-21T20:40:20+08:00 + + https://liudon.com/tags/%E5%B8%A6%E7%9D%80%E7%9B%B8%E6%9C%BA%E6%99%92%E5%A4%AA%E9%98%B3/ + 2022-07-21T20:40:20+08:00 + + https://liudon.com/posts/%E8%AE%B0%E7%AC%AC%E4%BA%8C%E6%AC%A1%E6%B4%97%E7%89%99/ + 2022-06-21T21:58:49+08:00 + + https://liudon.com/tags/%E5%8C%97%E4%BA%AC%E5%B9%BC%E5%8D%87%E5%B0%8F/ + 2022-05-25T20:10:01+08:00 + + https://liudon.com/tags/%E5%B9%BC%E5%8D%87%E5%B0%8F/ + 2022-05-25T20:10:01+08:00 + + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + 2022-05-25T20:10:01+08:00 + + https://liudon.com/tags/%E9%9D%9E%E4%BA%AC%E7%B1%8D/ + 2022-05-25T20:10:01+08:00 + + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + 2022-05-20T21:18:23+08:00 + + https://liudon.com/tags/%E5%8C%97%E4%BA%AC/ + 2022-05-20T19:17:57+08:00 + + https://liudon.com/tags/%E7%96%AB%E6%83%85/ + 2022-05-20T19:17:57+08:00 + + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + 2022-05-20T19:17:57+08:00 + + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2/ + 2022-05-13T18:20:52+08:00 + + https://liudon.com/tags/%E6%94%B6%E5%BD%95/ + 2022-05-13T18:20:52+08:00 + + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + 2022-05-13T18:20:52+08:00 + + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + 2022-05-05T20:22:23+08:00 + + https://liudon.com/tags/ssd/ + 2022-04-22T08:04:18+08:00 + + https://liudon.com/tags/thinkpad/ + 2022-04-22T08:04:18+08:00 + + https://liudon.com/tags/%E4%B8%89%E6%98%9F/ + 2022-04-22T08:04:18+08:00 + + https://liudon.com/tags/%E7%A1%AC%E7%9B%98/ + 2022-04-22T08:04:18+08:00 + + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + 2022-04-22T08:04:18+08:00 + + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + 2022-04-17T22:57:41+08:00 + + https://liudon.com/posts/%E5%B8%A6%E5%A8%83%E6%B8%B8%E9%A2%90%E5%92%8C%E5%9B%AD/ + 2022-04-11T00:37:55+08:00 + + https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/ + 2022-04-10T20:41:57+08:00 + + https://liudon.com/posts/%E9%9A%BE%E5%BE%97%E7%9A%84%E6%B8%85%E6%98%8E%E5%81%87%E6%9C%9F/ + 2022-04-06T08:06:19+08:00 + + https://liudon.com/posts/%E5%8D%81%E4%B8%80%E5%B9%B4%E7%9A%84%E7%AD%89%E5%BE%85%E7%BB%88%E4%BA%8E%E6%8B%BF%E5%88%B0%E4%BA%86liudon.com%E5%9F%9F%E5%90%8D/ + 2022-04-01T01:24:05+08:00 + + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + 2022-04-01T01:20:39+08:00 + + https://liudon.com/tags/mysql/ + 2020-12-14T18:47:29+08:00 + + https://liudon.com/posts/strings-and-numbers-in-mysql/ + 2020-12-14T18:47:29+08:00 + + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + 2020-05-19T17:16:53+08:00 + + https://liudon.com/tags/%E5%85%AC%E7%A7%AF%E9%87%91/ + 2020-01-17T17:14:32+08:00 + + https://liudon.com/posts/how-to-modify-marital-status/ + 2020-01-17T17:14:32+08:00 + + https://liudon.com/tags/php/ + 2019-11-26T19:56:18+08:00 + + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + 2019-11-26T19:56:18+08:00 + + https://liudon.com/tags/nginx/ + 2019-11-14T14:48:08+08:00 + + https://liudon.com/tags/ssl/ + 2019-11-14T14:48:08+08:00 + + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + 2019-11-14T14:48:08+08:00 + + https://liudon.com/posts/an-unforgettable-surgical-experience/ + 2019-10-28T19:03:31+08:00 + + https://liudon.com/posts/chinese-national-day/ + 2019-10-08T13:13:36+08:00 + + https://liudon.com/tags/swoft/ + 2019-09-26T13:14:23+08:00 + + https://liudon.com/posts/swoft-console-processor-analysis/ + 2019-09-26T13:14:23+08:00 + + https://liudon.com/posts/swoft-event-processor-analysis/ + 2019-09-26T13:02:18+08:00 + + https://liudon.com/tags/git/ + 2019-09-06T15:13:51+08:00 + + https://liudon.com/posts/a-issues-of-git-submodule-update/ + 2019-09-06T15:13:51+08:00 + + https://liudon.com/tags/curl/ + 2019-09-04T11:07:46+08:00 + + https://liudon.com/posts/curl-cost-time-long/ + 2019-09-04T11:07:46+08:00 + + https://liudon.com/posts/swoft-bean-processor-analysis/ + 2019-09-02T18:29:06+08:00 + + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + 2019-08-29T19:11:04+08:00 + + https://liudon.com/posts/swoft-execution-analysis/ + 2019-08-29T17:22:28+08:00 + + https://liudon.com/posts/bcmath-and-exponential-notation/ + 2019-08-16T19:34:34+08:00 + + https://liudon.com/tags/flink/ + 2019-03-28T13:00:50+08:00 + + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + 2019-03-28T13:00:50+08:00 + + https://liudon.com/posts/there-are-no-packages-available-for-installation/ + 2019-01-11T17:13:14+08:00 + + https://liudon.com/about/ + 2019-01-10T14:11:09+08:00 + + https://liudon.com/posts/the-first-post/ + 2019-01-09T15:17:04+08:00 + + https://liudon.com/search/ + + https://liudon.com/categories/ + + https://liudon.com/archives/ + + diff --git a/tags/cloudflare-pages/index.html b/tags/cloudflare-pages/index.html new file mode 100644 index 000000000..75aa99202 --- /dev/null +++ b/tags/cloudflare-pages/index.html @@ -0,0 +1,8 @@ +Cloudflare Pages | 流动 +

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      \ No newline at end of file diff --git a/tags/cloudflare-pages/index.xml b/tags/cloudflare-pages/index.xml new file mode 100644 index 000000000..183a3df5f --- /dev/null +++ b/tags/cloudflare-pages/index.xml @@ -0,0 +1,77 @@ + + + + Cloudflare Pages on 流动 + https://liudon.com/tags/cloudflare-pages/ + Recent content in Cloudflare Pages on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 29 Jul 2022 23:16:01 +0800 + + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      +
      +
      diff --git a/tags/cloudflare-pages/page/1/index.html b/tags/cloudflare-pages/page/1/index.html new file mode 100644 index 000000000..0cab860a0 --- /dev/null +++ b/tags/cloudflare-pages/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/cloudflare-pages/ + \ No newline at end of file diff --git a/tags/cloudflare/index.html b/tags/cloudflare/index.html new file mode 100644 index 000000000..cfce8567c --- /dev/null +++ b/tags/cloudflare/index.html @@ -0,0 +1,18 @@ +Cloudflare | 流动 +

      搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

      背景 4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。 +没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。 +...

      2024-05-22 · 3 min · 1262 words · Liudon

      加速Cloudflare访问

      背景 这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。 +众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。 +...

      2024-02-21 · 3 min · 1128 words · Liudon

      加速Google Analytics

      起因 Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。 +最近在优化页面访问速度,发现Google Analytics是一个优化点。 +优化 1. 访问加速 国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。 +...

      2023-12-02 · 2 min · 870 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon

      去掉Cloudflare烦人的email-decode.min.js请求

      通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。 +...

      2022-08-26 · 1 min · 180 words · Liudon

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      \ No newline at end of file diff --git a/tags/cloudflare/index.xml b/tags/cloudflare/index.xml new file mode 100644 index 000000000..6021d6461 --- /dev/null +++ b/tags/cloudflare/index.xml @@ -0,0 +1,703 @@ + + + + Cloudflare on 流动 + https://liudon.com/tags/cloudflare/ + Recent content in Cloudflare on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 22 May 2024 21:20:20 +0800 + + + 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + Wed, 22 May 2024 21:20:20 +0800 + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + <h4 id="背景">背景</h4> +<p>4月底的时候,Livid大佬提醒,<code>Cloudflare</code>应该是调整了<code>IPFS Gateway</code>网关策略,我的<a href="https://liudon.xyz">IPFS镜像博客</a>无法访问了。</p> +<p>没查到<code>Cloudflare</code>的调整说明,不过还好<code>IPFS</code>官方也提供了公共网关<code>gateway.ipfs.io</code>,将域名解析改到官网网关。</p> + 背景 +

      4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。

      +

      没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。

      +

      但还是无法访问,被Cloudflare拦截了。

      +
      Error 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC
      +CNAME Cross-User Banned
      +What happened?
      +You've requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare's security policy.
      +
      +What can I do?
      +If this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2's documentation for details.
      +
      +
      +Visit our website to learn more about Cloudflare.
      +

      这周在Discord群里,看到有人发消息,说是Cloudflare将下线IPFS Gateway网关服务。

      +

      https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard

      +
      +

      All traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!

      +
      +

      方案调研

      +

      经过一番搜索,找到了一篇自建IPFS Gateway网关的资料,里面用到了bifrost-gateway组件。

      +
      To run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:
      +
      +$ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
      +

      看文档,可以通过这个命令搭建一个自己的网关服务,同时支持DNSLink方式访问。

      +

      太棒了,感觉可以自己搭一套网关,然后用Nginx反代对外提供服务。

      +

      在之前将博客部署到星际文件系统(IPFS)文章中,已经通过Kubo搭建了一套本地IPFS服务。

      +

      上机器验证一下可行性:

      +
        +
      1. +

        启动Bifrost Gateway,网关默认地址为https://127.0.0.1:8081

        +
        $ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
        +2024/05/22 20:54:00 Starting bifrost-gateway dev-build
        +2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080
        +2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024
        +2024/05/22 20:54:00 GRAPH_BACKEND: false
        +2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001
        +2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081
        +2024/05/22 20:54:00   Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
        +2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081
        +2024/05/22 20:54:00   Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/
        +2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus
        +
      2. +
      3. +

        在另外一个终端下,执行命令

        +
        $ curl 'http://127.0.0.1:8081/' -H"Host:liudon.xyz" -I
        +HTTP/1.1 200 OK
        +Accept-Ranges: bytes
        +Access-Control-Allow-Headers: Content-Type
        +Access-Control-Allow-Headers: Range
        +Access-Control-Allow-Headers: User-Agent
        +Access-Control-Allow-Headers: X-Requested-With
        +Access-Control-Allow-Methods: GET
        +Access-Control-Allow-Methods: HEAD
        +Access-Control-Allow-Methods: OPTIONS
        +Access-Control-Allow-Origin: *
        +Access-Control-Expose-Headers: Content-Length
        +Access-Control-Expose-Headers: Content-Range
        +Access-Control-Expose-Headers: X-Chunked-Output
        +Access-Control-Expose-Headers: X-Ipfs-Path
        +Access-Control-Expose-Headers: X-Ipfs-Roots
        +Access-Control-Expose-Headers: X-Stream-Output
        +Content-Length: 26283
        +Content-Type: text/html
        +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
        +Last-Modified: Wed, 22 May 2024 12:57:29 GMT
        +X-Ipfs-Path: /ipns/liudon.xyz/
        +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
        +Date: Wed, 22 May 2024 12:57:29 GMT
        +
      4. +
      +

      验证可行,不过我记得Kubo默认就有网关服务的,试一下直接通过Kubo默认网关的情况。

      +

      Kubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用

      +
      $ curl 'http://127.0.0.1:8080/' -H"Host:liudon.xyz" -I
      +HTTP/1.1 200 OK
      +Accept-Ranges: bytes
      +Access-Control-Allow-Headers: Content-Type
      +Access-Control-Allow-Headers: Range
      +Access-Control-Allow-Headers: User-Agent
      +Access-Control-Allow-Headers: X-Requested-With
      +Access-Control-Allow-Methods: GET
      +Access-Control-Allow-Origin: *
      +Access-Control-Expose-Headers: Content-Length
      +Access-Control-Expose-Headers: Content-Range
      +Access-Control-Expose-Headers: X-Chunked-Output
      +Access-Control-Expose-Headers: X-Ipfs-Path
      +Access-Control-Expose-Headers: X-Ipfs-Roots
      +Access-Control-Expose-Headers: X-Stream-Output
      +Content-Length: 26283
      +Content-Type: text/html
      +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
      +Last-Modified: Wed, 22 May 2024 12:59:25 GMT
      +X-Ipfs-Path: /ipns/liudon.xyz/
      +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
      +Date: Wed, 22 May 2024 12:59:25 GMT
      +

      也是可以的,那就没必要多搞一套bifrost网关了。

      +

      具体实现

      +

      通过Nginx反向代理转发到本地IPFS网关,只需要改一下解析就可以继续使用IPFS服务了。

      +

      + +方案 + + +

      +
        +
      1. Nginx反向代理
      2. +
      +
      server {
      +    listen 443 ssl http2;
      +    server_name liudon.xyz;
      +
      +    ssl_certificate /etc/nginx/ssl/liudon.xyz/fullchain.cer;
      +    ssl_certificate_key /etc/nginx/ssl/liudon.xyz/liudon.xyz.key;
      +
      +    ssl_protocols TLSv1.2 TLSv1.3;
      +    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
      +    ssl_prefer_server_ciphers on;
      +    ssl_session_cache shared:SSL:10m;
      +    ssl_session_timeout 10m;
      +
      +    location / {
      +            proxy_pass http://127.0.0.1:8080;
      +            proxy_set_header Host $host; // 注意这里要传递反代的域名信息,限制只能访问我们自己dnslink对应的资源
      +    }
      +
      +    access_log /var/log/nginx/liudon.xyz.access.log;
      +    error_log /var/log/nginx/liudon.xyz.error.log;
      +}
      +

      申请Let's Encrypt证书,证书相关的就不多做介绍了,网上资料很多。

      +
        +
      1. 更改DNS解析
      2. +
      +
      原有的解析
      +
      +类型:CNAME
      +名称:liudon.xyz
      +内容:cloudflare-ipfs.com
      +
      +新的解析
      +
      +类型:A
      +名称:liudon.xyz
      +内容:你的服务器公网IP
      +

      搞定,又可以继续白嫖IPFS服务了。

      +]]>
      +
      + + 加速Cloudflare访问 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + Wed, 21 Feb 2024 20:25:49 +0800 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + <h4 id="背景">背景</h4> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="blog.png" width="828" height="753" alt="博客架构" title="" loading="lazy" /> + </picture> + +</p> +<p>这是当前的博客架构,文件保存在<code>Github</code>仓库,通过<code>Cloudflare Page</code>提供访问。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="slow.png" width="1842" height="531" alt="国内访问情况" title="" loading="lazy" /> + </picture> + +</p> +<p>众所周知,在国内,<code>Cloudflare</code>的CDN属于反向加速,平均耗时在1.5s左右。</p> + 背景 +

      + +博客架构 + + +

      +

      这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。

      +

      + +国内访问情况 + + +

      +

      众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。

      +

      今天,我们就来讲一下,如何实现国内海外双线路博客访问。

      +

      大体思路

      +
      海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。
      +
      +Cloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。
      +
      +国内CDN添加域名后,也会提供一个cname域名B。
      +
      +使用国内dns解析服务,配置cname双线路解析。
      +

      具体操作

      +
        +
      1. +

        Cloudflare Page添加新域名解析

        +

        这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。

        +

        + +Cloudflare Page添加域名 + + +

        +
      2. +
      3. +

        配置国内CDN

        +

        我用的腾讯云,其他服务商也是可以的。

        +

        + +添加CDN域名 + + +

        +
        加速域名:填写博客对外访问的域名
        +回源地址和Host:填写第一步新加的域名
        +

        添加成功后,会有一个cname地址,这里是国内线路解析要用到的。

        +
      4. +
      5. +

        DNS解析调整

        +

        Cloudflare不支持双线路配置,国内服务商支持,我这里用的是腾讯云。

        +

        首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到Cloudflare的邮件,不需要理会。

        +
        liudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:
        +
        +sandals.dnspod.net
        +heron.dnspod.net
        +[not set]
        +[not set]
        +[not set]
        +
        +此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。
        +

        然后添加解析,默认走国内CDN,境外走Cloudflare Page

        +

        + +DNS双线路解析 + + +

        +
      6. +
      +

      额外的问题

      +

      为了加速Google Analytics,使用Cloudflare Worker进行了反代,具体见加速Google Analytics

      +

      更改NS后,导致海外访问无法触发Cloudflare Worker了,导致没有博客统计数据了。

      +

      经过一番搜索后,发现Cloudflare Page有类似的Function功能,只需要在网站根目录下新建functions目录,添加对应文件即可。

      +

      这里以Hugo静态博客举例说明:

      +

      在根目录的static目录下,新建functions目录,新建analytics目录,添加post.js文件。

      +

      这个analytics/post.js是为了对应原有Worker的访问地址analytics/post,可自行修改。

      +

      post.js文件代码如下:

      +
      export async function onRequest(context) {
      +    try {
      +        return await postHandler(context);
      +    } catch(e) {
      +        return new Response(`${e.message}\n${e.stack}`, { status: 500 }); 
      +    }
      +}
      +
      +async function postHandler(context) {
      +    const GA_DOMAIN = 'google-analytics.com';
      +    const GA_COLLECT_PATH = 'g\/collect';
      +    const COLLECT_PATH = 'analytics/post';
      +    const DOMAIN = '这里填你博客的域名';
      +
      +    const url = context.request.url;
      +    const cf_ip = context.request.headers.get('CF-Connecting-IP');
      +    const cf_country = context.request.cf.country;
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`)
      +    const newReq = await readRequest(context.request, ga_url);
      +    context.waitUntil(fetch(newReq));
      +
      +    return new Response(null, {
      +        status: 204,
      +        statusText: 'No Content',
      +      });
      +}
      +
      +async function readRequest(request, url) {
      +    const { _, headers } = request;
      +    const nq = {
      +      method: request.method,
      +      headers: {
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: request.body,
      +    };
      +    return new Request(url, nq);
      +}
      +

      优化效果

      +

      + +优化后的访问 + + +

      +

      有了国内CDN的加持,平均耗时优化到1s左右了。

      +]]>
      +
      + + 加速Google Analytics + https://liudon.com/posts/optimize-google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/posts/optimize-google-analytics/ + <h3 id="起因">起因</h3> +<p><code>Google Analytics</code>是一款优秀的流量分析服务,集成方便,使用简单。</p> +<p>最近在优化页面访问速度,发现<code>Google Analytics</code>是一个优化点。</p> +<h3 id="优化">优化</h3> +<h4 id="1-访问加速">1. 访问加速</h4> +<p>国内访问<code>Google Analytics</code>很慢,同时还面临着各种广告屏蔽插件拦截。</p> + 起因 +

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      +

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      +

      优化

      +

      1. 访问加速

      +

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      +

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      +

      Cloudflare新建Worker,代码如下,保存后部署。

      +
      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      +
      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      +

      通过性能分析,发现js文件过大,影响页面加载速度。

      +

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      +

      搜索一番,找到一个瘦身版Google Analytics

      +
      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      +

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      +

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      +
      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      +

      + + + + +

      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      + + 去掉Cloudflare烦人的email-decode.min.js请求 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + Fri, 26 Aug 2022 23:25:57 +0800 + https://liudon.com/posts/remove-cloudflares-email-decode.min.js/ + <p>通过WebPageTest页面测试,发现一个<code>/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js</code>的文件请求,影响到了页面渲染。</p> + 通过WebPageTest页面测试,发现一个/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js的文件请求,影响到了页面渲染。

      +

      + +webpagetest测试结果 + + +

      +

      看路径像是Cloudflare的文件,搜了下主题代码,没找到相关文件。

      +

      经过一番搜索,原来这个是Cloudflare的电子邮件地址混淆技术功能。

      +

      + +Scrape Shield + + +

      +

      关闭这个功能即可。

      +]]>
      +
      + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      +
      +
      diff --git a/tags/cloudflare/page/1/index.html b/tags/cloudflare/page/1/index.html new file mode 100644 index 000000000..33d3e1320 --- /dev/null +++ b/tags/cloudflare/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/cloudflare/ + \ No newline at end of file diff --git a/tags/cls/index.html b/tags/cls/index.html new file mode 100644 index 000000000..91e834c41 --- /dev/null +++ b/tags/cls/index.html @@ -0,0 +1,12 @@ +CLS | 流动 +
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高. +问题表现 7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。 +偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。 +...

      2022-08-20 · 2 min · 886 words · Liudon
      \ No newline at end of file diff --git a/tags/cls/index.xml b/tags/cls/index.xml new file mode 100644 index 000000000..cced6c62f --- /dev/null +++ b/tags/cls/index.xml @@ -0,0 +1,253 @@ + + + + CLS on 流动 + https://liudon.com/tags/cls/ + Recent content in CLS on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 24 Aug 2022 12:37:22 +0800 + + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="../../posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 优化博客的累计布局偏移(CLS)问题 + https://liudon.com/posts/fix-blog-cls/ + Sat, 20 Aug 2022 07:27:22 +0800 + https://liudon.com/posts/fix-blog-cls/ + <p>此文已过期,优化方案参考<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>.</p> +<h4 id="问题表现">问题表现</h4> +<p>7月份将博客部署由<code>Github</code>迁移到<code>Cloudflare</code>后,开始关注博客的性能问题。</p> +<p>偶然看到苏卡卡大佬的<a href="https://blog.skk.moe/post/fix-blog-cls/">CLS优化文章</a>,拿自己博客也测试了下,发现也存在同样的问题。</p> + 此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      +

      问题表现

      +

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      +

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      +

      + +Lighthouse测试报告 + + +

      +

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      +

      为了解决这个问题,需要指定封面的宽高参数。

      +

      + +cover.html code + + +

      +

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      +

      解决方案

      +
        +
      1. +

        新增封面配置

        +

        文章封面配置新增widthheight属性。

        +
        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. +
      3. +

        自定义封面文件

        +

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

        +
            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        +

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        +

        + +cos-img-process + + +

        +
      4. +
      5. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      6. +
      +

      再进一步

      +

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      +

      基于markdown语法的图片代码,是不支持宽高参数的。

      +

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      +

      + +figure.html code + + +

      +

      我们使用figure语法插入图片,指定图片宽高。

      +

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

      +
          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      +
      +

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      +
      +

      + +gtmetrix-result + + +

      +

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      +]]>
      +
      +
      +
      diff --git a/tags/cls/page/1/index.html b/tags/cls/page/1/index.html new file mode 100644 index 000000000..5a4308576 --- /dev/null +++ b/tags/cls/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/cls/ + \ No newline at end of file diff --git a/tags/cors/index.html b/tags/cors/index.html new file mode 100644 index 000000000..2db2a403a --- /dev/null +++ b/tags/cors/index.html @@ -0,0 +1,9 @@ +Cors | 流动 +

      302跳转的跨域问题(CORS)

      302跳转的跨域问题 +场景一:302不返回跨域头 请求 +GET /302 HTTP/1.1 Host: liudon.xyz Origin: https://www.baidu.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 返回 +HTTP/1.1 200 OK Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Cf-Ray: 88535773eaf5107e-HKG Content-Length: 143 Content-Type: text/html Date: Fri, 17 May 2024 11:42:00 GMT Expires: Thu, 01 Jan 1970 00:00:01 GMT Location: https://liudon.org Server: cloudflare Vary: Accept-Encoding 浏览器报错 +...

      2024-05-17 · 2 min · 721 words · Liudon
      \ No newline at end of file diff --git a/tags/cors/index.xml b/tags/cors/index.xml new file mode 100644 index 000000000..a9ccd091f --- /dev/null +++ b/tags/cors/index.xml @@ -0,0 +1,136 @@ + + + + Cors on 流动 + https://liudon.com/tags/cors/ + Recent content in Cors on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 17 May 2024 20:13:57 +0800 + + + 302跳转的跨域问题(CORS) + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + Fri, 17 May 2024 20:13:57 +0800 + https://liudon.com/posts/the-cors-issue-with-302-redirect/ + <p>302跳转的跨域问题</p> +<h4 id="场景一302不返回跨域头">场景一:302不返回跨域头</h4> +<p><strong>请求</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>GET /302 HTTP/1.1 +</span></span><span style="display:flex;"><span>Host: liudon.xyz +</span></span><span style="display:flex;"><span>Origin: https://www.baidu.com +</span></span><span style="display:flex;"><span>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 +</span></span></code></pre></div><p><strong>返回</strong></p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>HTTP/1.1 200 OK +</span></span><span style="display:flex;"><span>Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 +</span></span><span style="display:flex;"><span>Cf-Ray: 88535773eaf5107e-HKG +</span></span><span style="display:flex;"><span>Content-Length: 143 +</span></span><span style="display:flex;"><span>Content-Type: text/html +</span></span><span style="display:flex;"><span>Date: Fri, 17 May 2024 11:42:00 GMT +</span></span><span style="display:flex;"><span>Expires: Thu, 01 Jan 1970 00:00:01 GMT +</span></span><span style="display:flex;"><span>Location: https://liudon.org +</span></span><span style="display:flex;"><span>Server: cloudflare +</span></span><span style="display:flex;"><span>Vary: Accept-Encoding +</span></span></code></pre></div><p><strong>浏览器报错</strong></p> + 302跳转的跨域问题

      +

      场景一:302不返回跨域头

      +

      请求

      +
      GET /302 HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.xyz/302' from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 + + +

      +

      场景二:302跳转返回跨域头

      +

      请求

      +
      GET /302_return_origin_header HTTP/1.1
      +Host: liudon.xyz
      +Origin: https://www.baidu.com
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      返回

      +
      HTTP/1.1 200 OK
      +Access-Control-Allow-Origin: https://www.baidu.com
      +Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
      +Cf-Ray: 88535773eaf5107e-HKG
      +Content-Length: 143
      +Content-Type: text/html
      +Date: Fri, 17 May 2024 11:42:00 GMT
      +Expires: Thu, 01 Jan 1970 00:00:01 GMT
      +Location: https://liudon.org
      +Server: cloudflare
      +Vary: Accept-Encoding
      +

      浏览器报错

      +
      Access to fetch at 'https://liudon.org/' (redirected from 'https://liudon.xyz/302_return_origin_header') from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
      +

      + +302 return origin header + + +

      +

      注意,这里302跳转请求没有报错,是跳转后的连接报了跨域错误。

      +

      Location请求

      +
      GET / HTTP/1.1
      +Host: liudon.org
      +Origin: null
      +Referer: https://www.baidu.com/
      +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
      +

      + +location + + +

      +

      302返回了跨域头,所以浏览器请求了Location地址。

      +

      但为什么两次请求header头里的Origin字段值不一致呢?第二次Location请求为什么Origin字段值是null?

      +
      第一次:
      +Origin: https://www.baidu.com
      +
      +第二次
      +Origin: null
      +

      经过一番搜索,终于找到了一些资料。

      +
      +

      The Origin header value may be null in a number of cases, including (non-exhaustively):

      +

      Origins whose scheme is not one of http, https, ftp, ws, wss, or gopher (including blob, file and data). +Cross-origin images and media data, including that in , and elements. +Documents created programmatically using createDocument(), generated from a data: URL, or that do not have a creator browsing context. +Redirects across origins. +iframes with a sandbox attribute that doesn’t contain the value allow-same-origin. +Responses that are network errors. +Referrer-Policy set to no-referrer for non-cors request modes (e.g. simple form posts).

      +
      +

      出自 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description

      +
      +

      A request request has a redirect-tainted origin if these steps return true:

      +

      Let lastURL be null.

      +

      For each url of request’s URL list:

      +

      If lastURL is null, then set lastURL to url and continue.

      +

      If url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true.

      +

      Set lastURL to url. +Return false. +Serializing a request origin, given a request request, is to run these steps:

      +

      If request has a redirect-tainted origin, then return “null”.

      +

      Return request’s origin, serialized.

      +
      +

      出自 https://fetch.spec.whatwg.org/#concept-request-tainted-origin

      +

      简单说就是如果302跳转的域与上一次请求域不同的话,就会将Origin设置为null

      +]]>
      +
      +
      +
      diff --git a/tags/cors/page/1/index.html b/tags/cors/page/1/index.html new file mode 100644 index 000000000..d82557ba6 --- /dev/null +++ b/tags/cors/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/cors/ + \ No newline at end of file diff --git a/tags/curl/index.html b/tags/curl/index.html new file mode 100644 index 000000000..d456a8787 --- /dev/null +++ b/tags/curl/index.html @@ -0,0 +1,8 @@ +Curl | 流动 +

      一个Curl的耗时长的问题

      发现某个接口请求很慢,但是后端确认接口是很快的。 +在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。 +业务里用到了Requests这个库,一开始以为是这个库导致的问题。 +...

      2019-09-04 · 2 min · 925 words · Liudon
      \ No newline at end of file diff --git a/tags/curl/index.xml b/tags/curl/index.xml new file mode 100644 index 000000000..3290e3f45 --- /dev/null +++ b/tags/curl/index.xml @@ -0,0 +1,118 @@ + + + + Curl on 流动 + https://liudon.com/tags/curl/ + Recent content in Curl on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 04 Sep 2019 11:07:46 +0800 + + + 一个Curl的耗时长的问题 + https://liudon.com/posts/curl-cost-time-long/ + Wed, 04 Sep 2019 11:07:46 +0800 + https://liudon.com/posts/curl-cost-time-long/ + <p>发现某个接口请求很慢,但是后端确认接口是很快的。</p> +<p>在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。</p> +<p>业务里用到了<code>Requests</code>这个库,一开始以为是这个库导致的问题。</p> + 发现某个接口请求很慢,但是后端确认接口是很快的。

      +

      在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。

      +

      业务里用到了Requests这个库,一开始以为是这个库导致的问题。

      +

      Requests_Transport_cURL类里断点定位了下,确实很慢,curl_getinfo返回的信息如下。

      +
      array (
      +  'url' => 'http://xxxxx',
      +  'content_type' => 'text/html',
      +  'http_code' => 200,
      +  'header_size' => 64,
      +  'request_size' => 305,
      +  'filetime' => -1,
      +  'ssl_verify_result' => 0,
      +  'redirect_count' => 0,
      +  'total_time' => 2.074094,
      +  'namelookup_time' => 2.5E-5,
      +  'connect_time' => 0.032107,
      +  'pretransfer_time' => 0.032109,
      +  'size_upload' => 186,
      +  'size_download' => 99,
      +  'speed_download' => 47,
      +  'speed_upload' => 89,
      +  'download_content_length' => 99,
      +  'upload_content_length' => 186,
      +  'starttransfer_time' => 2.032866,
      +  'redirect_time' => 0,
      +  'certinfo' =>
      +  array (
      +  ),
      +)
      +

      这里可以看到starttransfer_time时间很长。

      +

      搜索了一番,发现网上一个case,cURL slow starttransfer_time

      +

      里面提供了Expect: 100-continue这个header,又搜索了一番这个header资料。

      +

      curl在发POST请求的时候,如果body大于1k:

      +
        +
      1. 先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
      2. +
      3. 如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server; +如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。
      4. +
      +

      在机器上抓了个包,执行下面命令。

      +
      注意,下面port后面的80改成实际的端口
      +
      +tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
      +

      拿到的包信息。

      +
      09:17:19.421587 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306
      +E..f.m@.@...d}@.        A...XF.-.@...h....s.......
      +....T.0TPOST /cgi HTTP/1.1
      +User-Agent: php-requests/1.6
      +Accept: */*
      +Accept-Encoding: deflate, gzip
      +Referer: http://xxxxx:12345/cgi
      +Content-Length: 188
      +Expect: 100-continue
      +Content-Type: multipart/form-data; boundary=----------------------------ee2f4d848646
      +
      +
      +09:17:21.421786 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188
      +E....n@.@..Md}@.        A...XF.-.B...h....s./.....
      +....T.0[------------------------------ee2f4d848646
      +Content-Disposition: form-data; name="req"
      +
      +{"command":"zzz","appId":"yyyy"}
      +------------------------------ee2f4d848646--
      +
      +09:17:21.458628 IP xxxxx:12345 > xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117
      +E...X.@.5.Q2    A..d}@.F..X..h.-.B......3.....
      +T.2Q....HTTP/1.1 200 OK
      +Content-Type: text/html
      +Content-Length: 53
      +
      +{
      +    "data": [],
      +    "errno": 0,
      +    "error": "ok"
      +}
      +

      可以看到确实是先发了一个100-continue的请求,然后再发的实际POST请求。

      +

      在机器上执行下面的shell命令。

      +
      curl 'http://xxxxx:12345/cgi' -H"Expect: 100-continue" -v
      +

      返回如下,可以看到返回的header头里确实没有Expect这项。

      +
      * About to connect() to xxxxx port 12345 (#0)
      +*   Trying xxxxx...
      +* Connected to xxxxx (xxxxx) port 12345 (#0)
      +> GET /cloud_cgi HTTP/1.1
      +> User-Agent: curl/7.29.0
      +> Host: xxxxx:12345
      +> Accept: */*
      +> Expect: 100-continue
      +> 
      +< HTTP/1.1 200 OK
      +< Content-Type: text/html
      +< Content-Length: 42
      +< 
      +* Connection #0 to host xxxxx left intact
      +{"errno":100,"error":"参数格式错误"}
      +

      解决方法:

      +

      请求的时候,header里新增一项。

      +
      Expect:
      +
      ]]>
      +
      +
      +
      diff --git a/tags/curl/page/1/index.html b/tags/curl/page/1/index.html new file mode 100644 index 000000000..3e6e1b363 --- /dev/null +++ b/tags/curl/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/curl/ + \ No newline at end of file diff --git a/tags/filebase/index.html b/tags/filebase/index.html new file mode 100644 index 000000000..3c61b67f0 --- /dev/null +++ b/tags/filebase/index.html @@ -0,0 +1,7 @@ +Filebase | 流动 +

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。 +背景 周末更新博客时,发现workflow的上传IPFS任务执行失败了。 +...

      2024-09-04 · 3 min · 1246 words · Liudon
      \ No newline at end of file diff --git a/tags/filebase/index.xml b/tags/filebase/index.xml new file mode 100644 index 000000000..07a06164c --- /dev/null +++ b/tags/filebase/index.xml @@ -0,0 +1,138 @@ + + + + Filebase on 流动 + https://liudon.com/tags/filebase/ + Recent content in Filebase on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 04 Sep 2024 22:39:37 +0800 + + + 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + <p>本文会介绍如何接入<code>filebase</code>的Names(IPNS)服务,使你的<code>IPFS</code>站点持久在线。</p> +<h4 id="背景">背景</h4> +<p>周末更新博客时,发现workflow的上传IPFS任务执行失败了。</p> + 本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      +

      背景

      +

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      +
      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      +
      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      +

      Github文档,官方已经不再更新了。

      +
      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      +

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      +

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      +

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      +

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      +

      折腾记录

      +

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      +

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      +
      生成密钥
      +

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      +

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      +

      所以需要将云主机的密钥导出后,导入到filebase

      +

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      +
      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      +

      + +chat + + +

      +

      查看已有密钥:

      +
      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      +
      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      +

      filebase导入key要求为base64编码,将其转为base64编码:

      +
      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      +
      创建NAME
      +

      进入filebase控制台,点击Create Name

      +

      + +input + + +

      +
      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      +
      修改workflow
      +
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      +

      提交后,执行workflow,在执行结果里找到IPNS地址。

      +
      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +
      +

      更新域名的dnslink值:

      +

      普通域名

      +

      + +dns + + +

      +

      eth域名

      +

      + +eth + + +

      +

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      +

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      +]]>
      +
      +
      +
      diff --git a/tags/filebase/page/1/index.html b/tags/filebase/page/1/index.html new file mode 100644 index 000000000..d059092fa --- /dev/null +++ b/tags/filebase/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/filebase/ + \ No newline at end of file diff --git a/tags/flink/index.html b/tags/flink/index.html new file mode 100644 index 000000000..8f12b26e9 --- /dev/null +++ b/tags/flink/index.html @@ -0,0 +1,8 @@ +Flink | 流动 +

      Flink Could Not Resolve Resourcemanager Address

      什么是Flink。 +Apache Flink® - Stateful Computations over Data Streams +Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。 +...

      2019-03-28 · 2 min · 517 words · Liudon
      \ No newline at end of file diff --git a/tags/flink/index.xml b/tags/flink/index.xml new file mode 100644 index 000000000..a943034e7 --- /dev/null +++ b/tags/flink/index.xml @@ -0,0 +1,81 @@ + + + + Flink on 流动 + https://liudon.com/tags/flink/ + Recent content in Flink on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 28 Mar 2019 13:00:50 +0800 + + + Flink Could Not Resolve Resourcemanager Address + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + Thu, 28 Mar 2019 13:00:50 +0800 + https://liudon.com/posts/flink-could-not-resolve-resourcemanager-address/ + <p>什么是Flink。</p> +<blockquote> +<p>Apache Flink® - Stateful Computations over Data Streams</p> +</blockquote> +<p>Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。</p> + 什么是Flink。

      +
      +

      Apache Flink® - Stateful Computations over Data Streams

      +
      +

      Flink安装参考(官方文档)[https://ci.apache.org/projects/flink/flink-docs-release-1.7/tutorials/local_setup.html]。

      +

      这里使用单机模式

      +

      问题表现

      +

      启动Flink

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ./bin/start-cluster.sh 
      +Starting cluster.
      +Starting standalonesession daemon on host VM_80_180_centos.
      +Starting taskexecutor daemon on host VM_80_180_centos.
      +

      查看进程

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# jps
      +10442 StandaloneSessionClusterEntrypoint
      +11067 Jps
      +10909 TaskManagerRunner
      +[root@VM_80_180_centos /usr/local/flink-1.7.2]# 
      +

      查看日志,发现"Could not resolve ResourceManager address"的错误。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# tail -f log/flink-root-taskexecutor-*.log
      +
      +2019-03-27 19:43:23,804 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +2019-03-27 19:43:43,843 INFO  org.apache.flink.runtime.taskexecutor.TaskExecutor            - Could not resolve ResourceManager address akka.tcp://flink@localhost:6123/user/resourcemanager, retrying in 10000 ms: Ask timed out on [ActorSelection[Anchor(akka.tcp://flink@localhost:6123/), Path(/user/resourcemanager)]] after [10000 ms]. Sender[null] sent message of type "akka.actor.Identify"..
      +

      访问Flink的web页面,发现task数全为0.

      +

      + +flink no task + + +

      +

      问题原因:

      +

      + +taskmanager.host + + +

      +

      Flink的taskmanager.host默认为空,会使用hostname。

      +
      [root@VM_80_180_centos /usr/local/flink-1.7.2]# ping VM_80_180_centos
      +PING VM_80_180_centos (100.125.80.180) 56(84) bytes of data.
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=1 ttl=64 time=0.022 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=2 ttl=64 time=0.038 ms
      +64 bytes from VM_80_180_centos (100.125.80.180): icmp_seq=3 ttl=64 time=0.038 ms
      +

      Flink的jobmanager.host默认为localhost。

      +

      这里jobmanager和taskmanager绑定的ip不一样,导致出错。

      +

      解决办法:

      +
      vim conf/flink-conf.yaml
      +
      +添加下面这行配置
      +taskmanager.host: localhost
      +

      保存退出,然后重新启动Flink,这个时候在web端就可以看到有可用task了。

      +

      + +flink web + + +

      +]]>
      +
      +
      +
      diff --git a/tags/flink/page/1/index.html b/tags/flink/page/1/index.html new file mode 100644 index 000000000..8daa8d0c9 --- /dev/null +++ b/tags/flink/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/flink/ + \ No newline at end of file diff --git a/tags/follow/index.html b/tags/follow/index.html new file mode 100644 index 000000000..b5a3e16c7 --- /dev/null +++ b/tags/follow/index.html @@ -0,0 +1,8 @@ +Follow | 流动 +

      你好 Follow

      Follow: Next generation information browser. +最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。 +蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。 +...

      2024-09-17 · 1 min · 271 words · Liudon
      \ No newline at end of file diff --git a/tags/follow/index.xml b/tags/follow/index.xml new file mode 100644 index 000000000..ff63daa7f --- /dev/null +++ b/tags/follow/index.xml @@ -0,0 +1,51 @@ + + + + Follow on 流动 + https://liudon.com/tags/follow/ + Recent content in Follow on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 17 Sep 2024 00:53:38 +0800 + + + 你好 Follow + https://liudon.com/posts/hi-follow/ + Tue, 17 Sep 2024 00:53:38 +0800 + https://liudon.com/posts/hi-follow/ + <blockquote> +<p>Follow: Next generation information browser.</p> +</blockquote> +<p>最近博客圈开始流行<code>Follow邀请码</code>,大家各种求码,一码难求。</p> +<p>蹲在<code>Discord</code>群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。</p> + +

      Follow: Next generation information browser.

      + +

      最近博客圈开始流行Follow邀请码,大家各种求码,一码难求。

      +

      蹲在Discord群里一周,虽然时有发码,但最终还是狼多肉少,抢不到码呀。

      +

      上周五好不容易加上管理员,要到了一枚邀请码,终于可以激活体验了。

      +

      Follow里,订阅变的异常简单,输入url,它会自己检查rss订阅。

      +

      + +Follow + + +

      +

      另外发现我的博客,在Follow显示的内容不全。

      +

      检查了一下,发现是输出的RSS内容不全。

      +

      修改hugo配置文件,开启RSS输出全文。

      +
      ShowFullTextinRSS: true
      +

      从木木大佬那里看到,可以认证自己的Feed,我也来搞一下我的。

      +
      This message is used to verify that this feed (feedId:55815884011044914) belongs to me (userId:56204227179125760). 
      +Join me in enjoying the next generation information browser https://follow.is.
      +

      咱也是带标的了。

      +

      + +认证 + + +

      +]]>
      +
      +
      +
      diff --git a/tags/follow/page/1/index.html b/tags/follow/page/1/index.html new file mode 100644 index 000000000..0696acbed --- /dev/null +++ b/tags/follow/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/follow/ + \ No newline at end of file diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 000000000..9f4192cdb --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1,8 @@ +Git | 流动 +

      一个git submodule update引发的问题

      背景 1月份的时候,用hugo搭了这套博客系统。 +本机写md文件,更新到github,然后通过travis-ci自动发布。 +jane主题是通过git submodule引入的,.gitmodules文件内容。 +...

      2019-09-06 · 4 min · 1748 words · Liudon
      \ No newline at end of file diff --git a/tags/git/index.xml b/tags/git/index.xml new file mode 100644 index 000000000..1ed5c5b4c --- /dev/null +++ b/tags/git/index.xml @@ -0,0 +1,252 @@ + + + + Git on 流动 + https://liudon.com/tags/git/ + Recent content in Git on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 06 Sep 2019 15:13:51 +0800 + + + 一个git submodule update引发的问题 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + Fri, 06 Sep 2019 15:13:51 +0800 + https://liudon.com/posts/a-issues-of-git-submodule-update/ + <h4 id="背景">背景</h4> +<p>1月份的时候,用<code>hugo</code>搭了这套博客系统。</p> +<p>本机写md文件,更新到<code>github</code>,然后通过<code>travis-ci</code>自动发布。</p> +<p>jane主题是通过<code>git submodule</code>引入的,<code>.gitmodules</code>文件内容。</p> + 背景 +

      1月份的时候,用hugo搭了这套博客系统。

      +

      本机写md文件,更新到github,然后通过travis-ci自动发布。

      +

      jane主题是通过git submodule引入的,.gitmodules文件内容。

      +
      [submodule "themes/jane"]
      +	path = themes/jane
      +	url = https://github.com/xianmin/hugo-theme-jane.git
      +

      问题

      +

      最近几天更新完文章后,发现首页显示出了问题。

      +

      一开始以为是主题有问题,具体描述见首页文章不显示了

      +

      issue里: +shaform提到使用的并不是最新的版本。 +RocFang提到是git submodule使用的问题。

      +

      但是travis-ci每次都是通过git submodule update --init --recursive更新子仓库代码的,为什么会不是最新的代码呢。

      +

      问题重现

      +

      接下来,我们用一个新的仓库,来模拟重现一下。

      +
        +
      1. +

        克隆仓库。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +[root@VM_81_18_centos test]# 
        +
      2. +
      3. +

        添加文件。

        +
         [root@VM_81_18_centos xx]# cd test/
        + [root@VM_81_18_centos test]# echo "# test" >> README.md
        + [root@VM_81_18_centos test]# git add README.md
        + [root@VM_81_18_centos test]# 
        +
      4. +
      5. +

        引用子仓库。

        +
         [root@VM_81_18_centos test]# git submodule add git@github.com:xianmin/hugo-theme-jane.git theme/jane
        + Cloning into 'theme/jane'...
        + remote: Enumerating objects: 216, done.
        + remote: Counting objects: 100% (216/216), done.
        + remote: Compressing objects: 100% (128/128), done.
        + remote: Total 6165 (delta 102), reused 159 (delta 65), pack-reused 5949
        + Receiving objects: 100% (6165/6165), 3.05 MiB | 1.70 MiB/s, done.
        + Resolving deltas: 100% (3443/3443), done.
        +
      6. +
      7. +

        查看文件列表。

        +
         [root@VM_81_18_centos test]# ll
        + total 8
        + -rw-r--r-- 1 root root    5 Sep  6 16:05 README.md
        + drwxr-xr-x 7 root root 4096 Sep  6 16:08 typecho
        + [root@VM_81_18_centos test]# 
        +
      8. +
      9. +

        查看状态。

        +
        [root@VM_81_18_centos test]# git status
        +# On branch master
        +#
        +# Initial commit
        +#
        +# Changes to be committed:
        +#   (use "git rm --cached <file>..." to unstage)
        +#
        +#	new file:   .gitmodules
        +#	new file:   README.md
        +#	new file:   typecho
        +#
        +[root@VM_81_18_centos test]# 
        +
      10. +
      11. +

        查看修改。

        +
        [root@VM_81_18_centos test]# git diff --cached
        +diff --git a/.gitmodules b/.gitmodules
        +new file mode 100644
        +index 0000000..b1ddf70
        +--- /dev/null
        ++++ b/.gitmodules
        +@@ -0,0 +1,3 @@
        ++[submodule "typecho"]
        ++       path = typecho
        ++       url = https://github.com/Liudon/typecho
        +diff --git a/README.md b/README.md
        +new file mode 100644
        +index 0000000..9daeafb
        +--- /dev/null
        ++++ b/README.md
        +@@ -0,0 +1 @@
        ++test
        +diff --git a/typecho b/typecho
        +new file mode 160000
        +index 0000000..b0c4cc7
        +--- /dev/null
        ++++ b/typecho
        +@@ -0,0 +1 @@
        ++Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +[root@VM_81_18_centos test]# 
        +

        注意最后一行Subproject commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1

        +

        这个commitId是子仓库最新提交的记录id,对应的修改记录

        +
      12. +
      13. +

        提交修改。

        +
        [root@VM_81_18_centos test]# git push -u origin master
        +Counting objects: 4, done.
        +Compressing objects: 100% (3/3), done.
        +Writing objects: 100% (4/4), 362 bytes | 0 bytes/s, done.
        +Total 4 (delta 0), reused 0 (delta 0)
        +To git@github.com:Liudon/test.git
        +* [new branch]      master -> master
        +Branch master set up to track remote branch master from origin.
        +[root@VM_81_18_centos test]# 
        +

        + + +

        +

        提交后,在github上子仓库后面会多显示一个@xxxxx,这里就是引用的commitId,对应到前面git diff最后一行。

        +

        点击查看提交记录

        +

        + + +

        +

        本次提交的commitId5b11d515db8ad8d299ef1691f115590e0015c3b7,子仓库typecho单独记录了引入时的commitId,为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,对应的提交记录

        +
      14. +
      15. +

        接下来克隆子仓库,进行更新提交。

        +
        [root@VM_81_18_centos xx]# git clone git@github.com:Liudon/typecho.git
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 1, done.
        +remote: Counting objects: 100% (1/1), done.
        +remote: Total 7179 (delta 0), reused 0 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7179/7179), 7.26 MiB | 2.02 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +[root@VM_81_18_centos xx]# 
        +[root@VM_81_18_centos xx]# cd typecho/
        +[root@VM_81_18_centos typecho]# git log -n 1
        +commit b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1
        +Merge: c904005 8fd7492
        +Author: 祁宁 <magike.net@gmail.com>
        +Date:   Tue Nov 18 13:59:52 2014 +0800
        +
        +    Merge branch 'master' of https://github.com/typecho/typecho
        +[root@VM_81_18_centos typecho]#
        +

        通过git log,确认最新的提交commitId为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面的引入的一致。

        +
        [root@VM_81_18_centos typecho]# echo "xxx" > test
        +[root@VM_81_18_centos typecho]# 
        +[root@VM_81_18_centos typecho]# git add test
        +[root@VM_81_18_centos typecho]# git commit -m 'test'
        +[master 5dcc8f4] test
        +1 file changed, 1 insertion(+)
        +create mode 100644 test
        +[root@VM_81_18_centos typecho]# git push
        +Counting objects: 4, done.
        +Compressing objects: 100% (2/2), done.
        +Writing objects: 100% (3/3), 252 bytes | 0 bytes/s, done.
        +Total 3 (delta 1), reused 0 (delta 0)
        +remote: Resolving deltas: 100% (1/1), completed with 1 local object.
        +To git@github.com:Liudon/typecho.git
        +b0c4cc7..5dcc8f4  master -> master
        +[root@VM_81_18_centos typecho]# 
        +

        修改文件提交。

        +
        [root@VM_81_18_centos typecho]# git log -n 1
        +commit 5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2
        +Author: liudon <i.mu@qq.com>
        +Date:   Fri Sep 6 16:26:47 2019 +0800
        +
        +    test
        +[root@VM_81_18_centos typecho]#
        +

        最新提交的commitId5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2

        +
      16. +
      17. +

        重新克隆test库。

        +
        [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
        +Cloning into 'test'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
        +Receiving objects: 100% (4/4), done.
        +[root@VM_81_18_centos yy]# cd test/
        +[root@VM_81_18_centos test]# ll
        +total 8
        +-rw-r--r-- 1 root root    5 Sep  6 16:31 README.md
        +drwxr-xr-x 2 root root 4096 Sep  6 16:31 typecho
        +[root@VM_81_18_centos test]# ll typecho/
        +total 0
        +[root@VM_81_18_centos test]# 
        +

        这里可以看到typecho目录下是没有文件的。

        +
        [root@VM_81_18_centos test]# git submodule update --init --recursive
        +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
        +Cloning into 'typecho'...
        +remote: Enumerating objects: 4, done.
        +remote: Counting objects: 100% (4/4), done.
        +remote: Compressing objects: 100% (3/3), done.
        +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
        +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.26 MiB/s, done.
        +Resolving deltas: 100% (4844/4844), done.
        +Submodule path 'typecho': checked out 'b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1'
        +[root@VM_81_18_centos test]#
        +

        更新子仓库代码,这里可以看到最终checkout的版本为b0c4cc77a7f8f04661fb9f75d4ba6d4d7915b0f1,与前面提交时的版本一致。

        +
      18. +
      +

      问题分析

      +

      git submodule add的时候,会记录当时引入时子仓库的版本id。

      +

      git submodule update --init --recursive,会检出引入时的仓库版本,这就是为啥代码没有更新了。

      +

      问题解决

      +
      [root@VM_81_18_centos yy]# git clone git@github.com:Liudon/test.git
      +Cloning into 'test'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 4 (delta 0), reused 4 (delta 0), pack-reused 0
      +Receiving objects: 100% (4/4), done.
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# 
      +[root@VM_81_18_centos yy]# cd test/
      +[root@VM_81_18_centos test]# ll
      +total 8
      +-rw-r--r-- 1 root root    5 Sep  6 16:37 README.md
      +drwxr-xr-x 2 root root 4096 Sep  6 16:37 typecho
      +[root@VM_81_18_centos test]# ll typecho/
      +total 0
      +[root@VM_81_18_centos test]# 
      +[root@VM_81_18_centos test]# git submodule update --init --remote --recursive
      +Submodule 'typecho' (https://github.com/Liudon/typecho) registered for path 'typecho'
      +Cloning into 'typecho'...
      +remote: Enumerating objects: 4, done.
      +remote: Counting objects: 100% (4/4), done.
      +remote: Compressing objects: 100% (3/3), done.
      +remote: Total 7182 (delta 0), reused 2 (delta 0), pack-reused 7178
      +Receiving objects: 100% (7182/7182), 7.26 MiB | 1.24 MiB/s, done.
      +Resolving deltas: 100% (4844/4844), done.
      +Submodule path 'typecho': checked out '5dcc8f4e91cc724ba82aba5b9e7955727b58c5c2'
      +[root@VM_81_18_centos test]#
      +

      使用git submodule update --init --remote --recursive命令。

      +]]>
      +
      +
      +
      diff --git a/tags/git/page/1/index.html b/tags/git/page/1/index.html new file mode 100644 index 000000000..bd8e13617 --- /dev/null +++ b/tags/git/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/git/ + \ No newline at end of file diff --git a/tags/github-pages/index.html b/tags/github-pages/index.html new file mode 100644 index 000000000..47829edf2 --- /dev/null +++ b/tags/github-pages/index.html @@ -0,0 +1,11 @@ +Github Pages | 流动 +

      Github Pages 部署流程解析

      上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。 +看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。 +- name: Install dependencies run: pnpm install - name: Build run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload dist repository path: './dist' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 核心逻辑就是上面这段。 +...

      2024-09-24 · 2 min · 570 words · Liudon

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      \ No newline at end of file diff --git a/tags/github-pages/index.xml b/tags/github-pages/index.xml new file mode 100644 index 000000000..6364e03aa --- /dev/null +++ b/tags/github-pages/index.xml @@ -0,0 +1,145 @@ + + + + Github Pages on 流动 + https://liudon.com/tags/github-pages/ + Recent content in Github Pages on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 24 Sep 2024 21:30:21 +0800 + + + Github Pages 部署流程解析 + https://liudon.com/posts/github-pages-deployment-tutorial/ + Tue, 24 Sep 2024 21:30:21 +0800 + https://liudon.com/posts/github-pages-deployment-tutorial/ + <p>上周末在<a href="https://liudon.com/posts/building-a-workout-page/">搭建个人锻炼页面</a>时,遇到个<code>Github Pages</code>部署的困惑。</p> +<p>看了<code>running_page</code>项目文档,是支持部署到<code>Github Pages</code>页面的,对应的操作流程定义在<a href="https://github.com/yihong0618/running_page/blob/master/.github/workflows/gh-pages.yml">github/workflows/gh-pages.yml</a>文件。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Install dependencies +</span></span><span style="display:flex;"><span> run: pnpm install +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Build +</span></span><span style="display:flex;"><span> run: PATH_PREFIX<span style="color:#f92672">=/$</span>{{ github<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>repository<span style="color:#f92672">.</span>name }} pnpm build +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Upload artifact +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>upload<span style="color:#f92672">-</span>pages<span style="color:#f92672">-</span>artifact<span style="color:#960050;background-color:#1e0010">@</span>v3 +</span></span><span style="display:flex;"><span> with: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Upload dist repository</span> +</span></span><span style="display:flex;"><span> path: <span style="color:#e6db74">&#39;./dist&#39;</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">-</span> name: Deploy to GitHub Pages +</span></span><span style="display:flex;"><span> id: deployment +</span></span><span style="display:flex;"><span> uses: actions<span style="color:#f92672">/</span>deploy<span style="color:#f92672">-</span>pages<span style="color:#960050;background-color:#1e0010">@</span>v4 +</span></span></code></pre></div><p>核心逻辑就是上面这段。</p> + 上周末在搭建个人锻炼页面时,遇到个Github Pages部署的困惑。

      +

      看了running_page项目文档,是支持部署到Github Pages页面的,对应的操作流程定义在github/workflows/gh-pages.yml文件。

      +
          - name: Install dependencies
      +    run: pnpm install
      +    - name: Build
      +    run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build
      +
      +    - name: Upload artifact
      +    uses: actions/upload-pages-artifact@v3
      +    with:
      +        # Upload dist repository
      +        path: './dist'
      +    - name: Deploy to GitHub Pages
      +    id: deployment
      +    uses: actions/deploy-pages@v4
      +

      核心逻辑就是上面这段。

      +

      之前搞过部署hugo静态站点到Github Pages,使用的分支方式部署,编译后的静态文件单独用一个分支存放。

      +

      这里以我自己的博客项目举例,大致流程如下图:

      +

      + +github-pages-deploy-flow + + +

      +

      按我的理解,这里最终访问的文件内容是存在gh-page分支下的。

      +

      但是实际部署完running_page项目后,我发现并没有出现gh-page分支,但是Github Pages却可以正常访问。

      +

      有点不可思议,这个访问的数据是在哪里的呢?

      +

      带着这个疑问,在v2ex上发了个咨询贴

      +

      经过网友解惑,大致搞明白了这里的流程:

      +

      + +github-pages-deploy-flow + + +

      +

      Github Pages的发布源有两种方式,通过分支部署和通过Github Actions部署,分别对应上图的两条分支。

      +

      这里最终都会将build后的静态文件部署到Github Pages服务上,供用户访问。

      +

      分支部署的方式,其实是有一个内置工作流部署到Github Pages服务上的。

      +

      + + + + +

      +

      整个部署流程大致就是这样,最终的静态文件都是存在Github Pages服务上的。

      +]]>
      +
      + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      +
      +
      diff --git a/tags/github-pages/page/1/index.html b/tags/github-pages/page/1/index.html new file mode 100644 index 000000000..e58ecb696 --- /dev/null +++ b/tags/github-pages/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/github-pages/ + \ No newline at end of file diff --git a/tags/github/index.html b/tags/github/index.html new file mode 100644 index 000000000..0b8508a14 --- /dev/null +++ b/tags/github/index.html @@ -0,0 +1,16 @@ +Github | 流动 +

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。 +背景 周末更新博客时,发现workflow的上传IPFS任务执行失败了。 +...

      2024-09-04 · 3 min · 1246 words · Liudon

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。 +今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。 +...

      2023-10-27 · 2 min · 635 words · Liudon

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon

      利用Github Actions定时抓取微博

      背景 在微博上关注了一些用户,比如tk教主,月风。 +但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。 +实现方案 整体思路:利用Github Actions的Scheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。 +...

      2023-10-07 · 2 min · 823 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon
      \ No newline at end of file diff --git a/tags/github/index.xml b/tags/github/index.xml new file mode 100644 index 000000000..fa14e44f1 --- /dev/null +++ b/tags/github/index.xml @@ -0,0 +1,712 @@ + + + + Github on 流动 + https://liudon.com/tags/github/ + Recent content in Github on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 04 Sep 2024 22:39:37 +0800 + + + 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + <p>本文会介绍如何接入<code>filebase</code>的Names(IPNS)服务,使你的<code>IPFS</code>站点持久在线。</p> +<h4 id="背景">背景</h4> +<p>周末更新博客时,发现workflow的上传IPFS任务执行失败了。</p> + 本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      +

      背景

      +

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      +
      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      +
      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      +

      Github文档,官方已经不再更新了。

      +
      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      +

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      +

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      +

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      +

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      +

      折腾记录

      +

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      +

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      +
      生成密钥
      +

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      +

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      +

      所以需要将云主机的密钥导出后,导入到filebase

      +

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      +
      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      +

      + +chat + + +

      +

      查看已有密钥:

      +
      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      +
      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      +

      filebase导入key要求为base64编码,将其转为base64编码:

      +
      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      +
      创建NAME
      +

      进入filebase控制台,点击Create Name

      +

      + +input + + +

      +
      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      +
      修改workflow
      +
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      +

      提交后,执行workflow,在执行结果里找到IPNS地址。

      +
      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +
      +

      更新域名的dnslink值:

      +

      普通域名

      +

      + +dns + + +

      +

      eth域名

      +

      + +eth + + +

      +

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      +

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      +]]>
      +
      + + 使用Google Indexing API加速博客收录 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + <p>对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。</p> +<p>今天,我们就来介绍一种利用<code>Google Indexing API</code>接口,通过<code>Github Actions</code>实现部署时通知<code>Google</code>抓取页面内容。</p> + 对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      +

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      +

      操作步骤:

      +
        +
      1. +

        申请Google API凭证

        +

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        +

        + + + + +

        +

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        +

        + + + + +

        +
        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        +

        + + + + +

        +

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        +

        点击后面的三个点按钮,选择管理密钥。

        +

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        +

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

        +
      2. +
      3. +

        将服务账号关联到Google Search Console

        +

        进入Google Search Console控制台,选择你的网站。

        +

        找到设置里的用户和权限,点击添加用户。

        +

        + + + + +

        +
        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      4. +
      5. +

        配置Github Action

        +
          +
        • +

          添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

          +
        • +
        • +

          编辑workflow编排任务,新增步骤

          +
        • +
        +
        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

        + + + + +

        +
      6. +
      +]]>
      +
      + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      + + 利用Github Actions定时抓取微博 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + Sat, 07 Oct 2023 13:17:57 +0800 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + <h4 id="背景">背景</h4> +<p>在微博上关注了一些用户,比如<a href="https://weibo.com/u/1401527553">tk教主</a>,<a href="https://weibo.com/u/1670659923">月风</a>。</p> +<p>但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。</p> +<h4 id="实现方案">实现方案</h4> +<p><strong>整体思路:利用<code>Github Actions</code>的<code>Scheduled</code>任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。</strong></p> + 背景 +

      在微博上关注了一些用户,比如tk教主月风

      +

      但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。

      +

      实现方案

      +

      整体思路:利用Github ActionsScheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。

      +
        +
      1. +

        新建仓库,比如weibo_archive

        +
      2. +
      3. +

        添加抓取脚本,完整代码

        +

        这里用到微博两个接口:

        +
        // 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断
        +https://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom&type=uid&value=$uid&containerid=107603$uid
        +
        +// 根据微博id,抓取微博完整的内容
        +https://m.weibo.cn/statuses/extend?id=$id
        +
      4. +
      5. +

        添加环境变量。

        +
          +
        • +

          进入个人设置->Developer Settings->Personal access tokens->Tokens (classic),创建新的Token,记下对应的值。

          +
        • +
        • +

          进入第一步创建仓库的配置页,点击Secrets and variables下的Actions

          +

          切到Secret目录,创建新的Secret变量,名称为TOKEN,值为前一步记录的值;切到Variables目录,创建新的Variables变量,名称为WEIBO_UIDS,值为你需要抓取的微博用户id,多个用户的话以|分割。

          +
        • +
        +
      6. +
      7. +

        添加定时任务,完整yaml文件如下。

        +
        # This is a basic workflow to help you get started with Actions
        +
        +name: CI
        +
        +# Controls when the workflow will run
        +on:
        +# Triggers the workflow on push or pull request events but only for the "main" branch
        +push:
        +    branches: [ "main" ]
        +pull_request:
        +    branches: [ "main" ]
        +
        +# Allows you to run this workflow manually from the Actions tab
        +workflow_dispatch:
        +schedule:
        +    - cron: '*/5 * * * *'
        +
        +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
        +jobs:
        +# This workflow contains a single job called "build"
        +build:
        +    # The type of runner that the job will run on
        +    runs-on: ubuntu-latest
        +
        +    permissions:      
        +    contents: write
        +
        +    # Steps represent a sequence of tasks that will be executed as part of the job
        +    steps:
        +    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
        +    - uses: actions/checkout@v3
        +
        +    # Runs a single command using the runners shell
        +    - name: archive weibo
        +        run: |
        +        chmod +x ./weibo_archive.sh
        +        ./weibo_archive.sh
        +        shell: bash
        +        env:
        +        weibo_uids: ${{ vars.weibo_uids }}
        +
        +    # Runs a set of commands using the runners shell
        +    - name: Commit changes
        +        uses: EndBug/add-and-commit@v9
        +        env:
        +        github_token: ${{ secrets.TOKEN }}
        +        add: .
        +
      8. +
      +

      效果

      +

      抓取后的内容,会按用户id分别保存到不同文件。

      +

      + +效果 + + +

      +

      不过这个方案有一个唯一的缺点,Github Actions定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。

      +
      +

      Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.

      +
      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      +
      +
      diff --git a/tags/github/page/1/index.html b/tags/github/page/1/index.html new file mode 100644 index 000000000..f29a93ba3 --- /dev/null +++ b/tags/github/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/github/ + \ No newline at end of file diff --git a/tags/go/index.html b/tags/go/index.html new file mode 100644 index 000000000..df99e95dd --- /dev/null +++ b/tags/go/index.html @@ -0,0 +1,7 @@ +Go | 流动 +

      解决 "undeclared name: any (requires version go1.18 or later)" 编译错误

      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ $ protoc-gen-go --version protoc-gen-go v1.34.2 $ $ sh make.sh user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) $ 流水线编译报错,其中make.sh文件代码: +... protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto ... go build 同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。 +...

      2024-06-14 · 1 min · 473 words · Liudon
      \ No newline at end of file diff --git a/tags/go/index.xml b/tags/go/index.xml new file mode 100644 index 000000000..94ae3f84a --- /dev/null +++ b/tags/go/index.xml @@ -0,0 +1,72 @@ + + + + Go on 流动 + https://liudon.com/tags/go/ + Recent content in Go on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 14 Jun 2024 20:41:20 +0800 + + + 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ protoc-gen-go --version +</span></span><span style="display:flex;"><span>protoc-gen-go v1.34.2 +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ sh make.sh +</span></span><span style="display:flex;"><span>user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) +</span></span><span style="display:flex;"><span>$ +</span></span></code></pre></div><p>流水线编译报错,其中<code>make.sh</code>文件代码:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build +</span></span></code></pre></div><p>同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。</p> +
      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +$ 
      +$ protoc-gen-go --version
      +protoc-gen-go v1.34.2
      +$ 
      +$ sh make.sh
      +user.pb.go:123:45: undeclared name: any (requires version go1.18 or later)
      +$ 
      +

      流水线编译报错,其中make.sh文件代码:

      +
      ...
      +
      +protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto 
      +
      +...
      +
      +go build
      +

      同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。

      +

      登到流水线编译机器上,看了下go的版本已经是1.18.1了,按理不应该报这个错误的。

      +

      关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用master分支编译了一下,也还是报这个错误。

      +

      手动执行make.sh里的每条命令,发现是protoc编译pb文件时报的这个错误。

      +

      经过一番查找后,发现是protoc-gen-go在4月份更新了版本,引入了新特性。

      +

      protoc-gen-go’s versions

      +
      Versions in this module
      +v1
      +    v1.34.2 Jun 11, 2024
      +    v1.34.1 May 6, 2024
      +    v1.34.0 Apr 30, 2024
      +    v1.33.0 Mar 5, 2024
      +    v1.32.0 Dec 22, 2023
      +

      Protobuf Editions Overview

      +
      +

      Protobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = “proto2” or syntax = “proto3” at the top of proto definition files, you use an edition number, such as edition = “2024”, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.

      +

      Instead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.

      +
      +

      改用历史版本后解决。

      +
      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
      +
      ]]>
      +
      +
      +
      diff --git a/tags/go/page/1/index.html b/tags/go/page/1/index.html new file mode 100644 index 000000000..a1c39ba22 --- /dev/null +++ b/tags/go/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/go/ + \ No newline at end of file diff --git a/tags/golang/index.html b/tags/golang/index.html new file mode 100644 index 000000000..66e8466a9 --- /dev/null +++ b/tags/golang/index.html @@ -0,0 +1,12 @@ +Golang | 流动 +

      解决Golang使用go get安装包后找不到可执行文件的问题

      背景 编译流水线代码 +go get google.golang.org/protobuf/cmd/protoc-gen-go@latest protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto ... go build -o xxx 在go升级到1.20.1版本后,执行报错。 +protoc-gen-go: program not found or is not executable 解决 Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead. +In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled. +...

      2023-08-17 · 1 min · 195 words · Liudon

      Golang解析json的一个问题

      业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题: +请求接口,按返回包字段判断请求成功与否。 伪代码如下: +package main import ( "encoding/json" "fmt" ) type Response struct { Code int `json:"code"` Msg string `json:"msg"` } func main() { // 场景1,返回包符合接口要求 str := `{"code":100,"msg":"failed"}` var res Response json.Unmarshal([]byte(str), &res) fmt.Printf("res=%+v\n", res) // 解析正确,符合预期 // res={Code:100 Msg:failed} // 场景2,返回包不符合接口要求,缺少相关字段 str = `{"retCode":100,"retMsg":"failed"}` var res1 Response json.Unmarshal([]byte(str), &res1) fmt.Printf("res=%+v\n", res1) // 解析错误,不符合预期 // res={Code:0 Msg:} } 这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。 +...

      2022-05-20 · 1 min · 424 words · Liudon
      \ No newline at end of file diff --git a/tags/golang/index.xml b/tags/golang/index.xml new file mode 100644 index 000000000..8d4eff081 --- /dev/null +++ b/tags/golang/index.xml @@ -0,0 +1,172 @@ + + + + Golang on 流动 + https://liudon.com/tags/golang/ + Recent content in Golang on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 17 Aug 2023 09:20:50 +0800 + + + 解决Golang使用go get安装包后找不到可执行文件的问题 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + Thu, 17 Aug 2023 09:20:50 +0800 + https://liudon.com/posts/golang-go-get-binary-not-found-solution/ + <h4 id="背景">背景</h4> +<p>编译流水线代码</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go get google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build -o xxx +</span></span></code></pre></div><p>在go升级到1.20.1版本后,执行报错。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protoc-gen-go: program not found or is not executable +</span></span></code></pre></div><h4 id="解决">解决</h4> +<blockquote> +<p>Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.</p> +<p>In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.</p> + 背景 +

      编译流水线代码

      +
      go get google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +
      +protoc -I=./zzz --proto_path=./xx --go_out=./abc --go_opt=paths=xx.proto
      +
      +...
      +
      +go build -o xxx
      +

      在go升级到1.20.1版本后,执行报错。

      +
      protoc-gen-go: program not found or is not executable
      +

      解决

      +
      +

      Starting in Go 1.17, installing executables with go get is deprecated. go install may be used instead.

      +

      In a future Go release, go get will no longer build packages; it will only be used to add, update, or remove dependencies in go.mod. Specifically, go get will act as if the -d flag were enabled.

      +

      https://docs.studygolang.com/doc/go-get-install-deprecation

      +
      +

      从 Go 1.7 版本开始,go get 命令默认只会下载包,不会自动编译和安装可执行文件。

      +

      因此,如果你想要使用 go get 命令安装包并编译可执行文件,你需要使用 go install 命令。

      +

      替换为go install解决。

      +]]>
      +
      + + Golang解析json的一个问题 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + Fri, 20 May 2022 21:18:23 +0800 + https://liudon.com/posts/golang%E8%A7%A3%E6%9E%90json%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98/ + <p>业务模块从<code>php</code>迁移到<code>golang</code>下了,最近遇到一个golang下json解析的问题:</p> +<pre><code>请求接口,按返回包字段判断请求成功与否。 +</code></pre> +<p>伪代码如下:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;encoding/json&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;fmt&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Response</span> <span style="color:#66d9ef">struct</span> { +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Code</span> <span style="color:#66d9ef">int</span> <span style="color:#e6db74">`json:&#34;code&#34;`</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">Msg</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;msg&#34;`</span> +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() { +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景1,返回包符合接口要求 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;code&#34;:100,&#34;msg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析正确,符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:100 Msg:failed} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 场景2,返回包不符合接口要求,缺少相关字段 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">str</span> = <span style="color:#e6db74">`{&#34;retCode&#34;:100,&#34;retMsg&#34;:&#34;failed&#34;}`</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">res1</span> <span style="color:#a6e22e">Response</span> +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">str</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;res=%+v\n&#34;</span>, <span style="color:#a6e22e">res1</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 解析错误,不符合预期 +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// res={Code:0 Msg:} +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>} +</span></span></code></pre></div><p>这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。</p> + 业务模块从php迁移到golang下了,最近遇到一个golang下json解析的问题:

      +
      请求接口,按返回包字段判断请求成功与否。
      +
      +

      伪代码如下:

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code int    `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 场景1,返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +    // 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	// 场景2,返回包不符合接口要求,缺少相关字段
      +	str = `{"retCode":100,"retMsg":"failed"}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +    // 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +}
      +

      这里由于接口地址配置错误,导致请求到其他接口,返回包不符合协议要求。

      +

      缺少code字段,Unmarshal解析后,按默认值处理,所以code为0,导致验证出错。

      +

      修正方案:

      +

      Code字段定义为引用类型,通过判断地址是否为nil来区分缺少字段的情况。

      +
      package main
      +
      +import (
      +	"encoding/json"
      +	"fmt"
      +)
      +
      +type Response struct {
      +	Code *int   `json:"code"`
      +	Msg  string `json:"msg"`
      +}
      +
      +func main() {
      +	// 返回包符合接口要求
      +	str := `{"code":100,"msg":"failed"}`
      +	var res Response
      +	json.Unmarshal([]byte(str), &res)
      +	fmt.Printf("res=%+v\n", res)
      +	// 解析正确,符合预期
      +	// res={Code:100 Msg:failed}
      +
      +	if res.Code == nil || *res.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +
      +	// 返回包不符合接口要求,缺少相关字段
      +	str = `{"id":1}`
      +	var res1 Response
      +	json.Unmarshal([]byte(str), &res1)
      +	fmt.Printf("res=%+v\n", res1)
      +	// 解析错误,不符合预期
      +	// res={Code:0 Msg:}
      +
      +	if res1.Code == nil || *res1.Code > 0 {
      +		fmt.Println("request failed")
      +	}
      +}
      +

      参考资料:

      +

      how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided

      +

      json-key-not-set-null-golang

      +]]>
      +
      +
      +
      diff --git a/tags/golang/page/1/index.html b/tags/golang/page/1/index.html new file mode 100644 index 000000000..c1f566f51 --- /dev/null +++ b/tags/golang/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/golang/ + \ No newline at end of file diff --git a/tags/google-adsense/index.html b/tags/google-adsense/index.html new file mode 100644 index 000000000..93db850e9 --- /dev/null +++ b/tags/google-adsense/index.html @@ -0,0 +1,7 @@ +Google Adsense | 流动 +

      Google Adsense的审核之旅

      中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。 +偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。 +...

      2024-09-16 · 1 min · 411 words · Liudon
      \ No newline at end of file diff --git a/tags/google-adsense/index.xml b/tags/google-adsense/index.xml new file mode 100644 index 000000000..bda1cbd13 --- /dev/null +++ b/tags/google-adsense/index.xml @@ -0,0 +1,41 @@ + + + + Google Adsense on 流动 + https://liudon.com/tags/google-adsense/ + Recent content in Google Adsense on 流动 + Hugo -- 0.134.3 + zh-cn + Mon, 16 Sep 2024 23:18:50 +0800 + + + Google Adsense的审核之旅 + https://liudon.com/posts/my-google-adsense-approval-journey/ + Mon, 16 Sep 2024 23:18:50 +0800 + https://liudon.com/posts/my-google-adsense-approval-journey/ + <p>中午的时候,突然收到一条消息,打开一看,提示我的<code>Google Adsense</code>审核通过了。</p> +<p>偶然发现<code>Google Adsense</code>里居然有40美金,想起来是之前<a href="https://liudon.org">老博客</a>加的广告。</p> + 中午的时候,突然收到一条消息,打开一看,提示我的Google Adsense审核通过了。

      +

      偶然发现Google Adsense里居然有40美金,想起来是之前老博客加的广告。

      +

      看着新博客每天也有了一些访问,打算申请Google Adsense,补充些维护成本。

      +

      按之前的流程搞了一遍,提交了申请。

      +

      结果过了1周多,收到审核不通过邮件,说是不符合规范:低质内容,质量不高。

      +

      搜了一下,说是现在新网站审核门槛高了。

      +

      不放弃,继续申请呗。

      +

      + +approval google adsense + + +

      +

      从3月份开始,申请了7次,全部被拒。

      +

      尤其是8月25日被拒后,提示我审核次数过多,必须得等到8月31日以后才能再次申请。

      +

      上社区发了帖子,咨询到底是什么原因,结果也没收到答复。

      +

      这个时候,就已经有点心灰意冷,想要放弃了。

      +

      9月5日的时候,想着再最后申请一把试试看,再不通过就算了。

      +

      等了1周多,感觉这次估计又悬了,已经放弃了,结果今天竟然审核通过了。

      +

      历经了8次申请,耗时半年,终于申请下来了,算是这段时间难得的一件好事。

      +]]>
      +
      +
      +
      diff --git a/tags/google-adsense/page/1/index.html b/tags/google-adsense/page/1/index.html new file mode 100644 index 000000000..452b468b5 --- /dev/null +++ b/tags/google-adsense/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/google-adsense/ + \ No newline at end of file diff --git a/tags/google-analytics/index.html b/tags/google-analytics/index.html new file mode 100644 index 000000000..01e55e6aa --- /dev/null +++ b/tags/google-analytics/index.html @@ -0,0 +1,8 @@ +Google Analytics | 流动 +

      加速Google Analytics

      起因 Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。 +最近在优化页面访问速度,发现Google Analytics是一个优化点。 +优化 1. 访问加速 国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。 +...

      2023-12-02 · 2 min · 870 words · Liudon
      \ No newline at end of file diff --git a/tags/google-analytics/index.xml b/tags/google-analytics/index.xml new file mode 100644 index 000000000..c537a12a7 --- /dev/null +++ b/tags/google-analytics/index.xml @@ -0,0 +1,129 @@ + + + + Google Analytics on 流动 + https://liudon.com/tags/google-analytics/ + Recent content in Google Analytics on 流动 + Hugo -- 0.134.3 + zh-cn + Sat, 02 Dec 2023 09:25:49 +0800 + + + 加速Google Analytics + https://liudon.com/posts/optimize-google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/posts/optimize-google-analytics/ + <h3 id="起因">起因</h3> +<p><code>Google Analytics</code>是一款优秀的流量分析服务,集成方便,使用简单。</p> +<p>最近在优化页面访问速度,发现<code>Google Analytics</code>是一个优化点。</p> +<h3 id="优化">优化</h3> +<h4 id="1-访问加速">1. 访问加速</h4> +<p>国内访问<code>Google Analytics</code>很慢,同时还面临着各种广告屏蔽插件拦截。</p> + 起因 +

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      +

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      +

      优化

      +

      1. 访问加速

      +

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      +

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      +

      Cloudflare新建Worker,代码如下,保存后部署。

      +
      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      +
      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      +

      通过性能分析,发现js文件过大,影响页面加载速度。

      +

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      +

      搜索一番,找到一个瘦身版Google Analytics

      +
      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      +

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      +

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      +
      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      +

      + + + + +

      +]]>
      +
      +
      +
      diff --git a/tags/google-analytics/page/1/index.html b/tags/google-analytics/page/1/index.html new file mode 100644 index 000000000..93d735e02 --- /dev/null +++ b/tags/google-analytics/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/google-analytics/ + \ No newline at end of file diff --git a/tags/google/index.html b/tags/google/index.html new file mode 100644 index 000000000..bddc65ab1 --- /dev/null +++ b/tags/google/index.html @@ -0,0 +1,7 @@ +Google | 流动 +

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。 +今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。 +...

      2023-10-27 · 2 min · 635 words · Liudon
      \ No newline at end of file diff --git a/tags/google/index.xml b/tags/google/index.xml new file mode 100644 index 000000000..ddbff8ec9 --- /dev/null +++ b/tags/google/index.xml @@ -0,0 +1,99 @@ + + + + Google on 流动 + https://liudon.com/tags/google/ + Recent content in Google on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 27 Oct 2023 19:32:24 +0800 + + + 使用Google Indexing API加速博客收录 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + <p>对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。</p> +<p>今天,我们就来介绍一种利用<code>Google Indexing API</code>接口,通过<code>Github Actions</code>实现部署时通知<code>Google</code>抓取页面内容。</p> + 对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      +

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      +

      操作步骤:

      +
        +
      1. +

        申请Google API凭证

        +

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        +

        + + + + +

        +

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        +

        + + + + +

        +
        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        +

        + + + + +

        +

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        +

        点击后面的三个点按钮,选择管理密钥。

        +

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        +

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

        +
      2. +
      3. +

        将服务账号关联到Google Search Console

        +

        进入Google Search Console控制台,选择你的网站。

        +

        找到设置里的用户和权限,点击添加用户。

        +

        + + + + +

        +
        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      4. +
      5. +

        配置Github Action

        +
          +
        • +

          添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

          +
        • +
        • +

          编辑workflow编排任务,新增步骤

          +
        • +
        +
        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

        + + + + +

        +
      6. +
      +]]>
      +
      +
      +
      diff --git a/tags/google/page/1/index.html b/tags/google/page/1/index.html new file mode 100644 index 000000000..10c6300b8 --- /dev/null +++ b/tags/google/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/google/ + \ No newline at end of file diff --git a/tags/gorm/index.html b/tags/gorm/index.html new file mode 100644 index 000000000..a7b9837ab --- /dev/null +++ b/tags/gorm/index.html @@ -0,0 +1,10 @@ +Gorm | 流动 +

      GORM增加sqlcommenter特性

      什么是sqlcommenter? +sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side. +...

      2024-04-18 · 2 min · 742 words · Liudon

      源码分析:GORM是如何生成sql的

      在gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。 +gorm使用示例 +package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) type Product struct { gorm.Model Code string Price uint } func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) var product Product db.First(&product, 1) // 根据整型主键查找 } 我们以First查询为例,看一下是怎么转成具体sql的。 +...

      2024-04-18 · 6 min · 2737 words · Liudon
      \ No newline at end of file diff --git a/tags/gorm/index.xml b/tags/gorm/index.xml new file mode 100644 index 000000000..91776ded1 --- /dev/null +++ b/tags/gorm/index.xml @@ -0,0 +1,930 @@ + + + + Gorm on 流动 + https://liudon.com/tags/gorm/ + Recent content in Gorm on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 18 Apr 2024 21:25:24 +0800 + + + GORM增加sqlcommenter特性 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + <p>什么是sqlcommenter?</p> +<blockquote> +<p>sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.</p> + 什么是sqlcommenter?

      +
      +

      sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

      +
      +

      GORM提供了hints组件,可以支持sqlcommenter

      +
      import "gorm.io/hints"
      +
      +DB.Clauses(hints.Comment("select", "master")).Find(&User{})
      +// SELECT /*master*/ * FROM `users`;
      +
      +DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
      +// SELECT * FROM `users` WHERE id = ? /* hint */
      +

      但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

      +

      我希望是全局增加sqlcommenter,业务侧不需要过多调整。

      +

      完整代码如下:

      +
      plugins/gorm.go
      +
      +package plugins
      +
      +import (
      +	"fmt"
      +
      +	gorm "gorm.io/gorm"
      +	gormclause "gorm.io/gorm/clause"
      +)
      +
      +type Comment struct {
      +	Content string
      +}
      +
      +func (c Comment) Name() string {
      +	return "COMMENT"
      +}
      +
      +func (c Comment) Build(builder gormclause.Builder) {
      +	builder.WriteString("/* ")
      +	builder.WriteString(c.Content)
      +	builder.WriteString(" */")
      +}
      +
      +func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
      +}
      +
      +func (c Comment) ModifyStatement(stmt *gorm.Statement) {
      +	clause := stmt.Clauses[c.Name()]
      +    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
      +    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
      +	clause.Expression = c
      +	stmt.Clauses[c.Name()] = clause
      +}
      +
      +var extraClause = []string{"COMMENT"}
      +
      +type CommentClausePlugin struct{}
      +
      +// NewCommentClausePlugin create a new ExtraPlugin
      +func NewCommentClausePlugin() *CommentClausePlugin {
      +	return &CommentClausePlugin{}
      +}
      +
      +// Name plugin name
      +func (ep *CommentClausePlugin) Name() string {
      +	return "CommentClausePlugin"
      +}
      +
      +// Initialize register BuildClauses
      +func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
      +	initClauses(db)
      +	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)
      +
      +	return
      +}
      +
      +func AddAnnotation(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +
      +	rid := "xx"
      +	// context上下文里取rid信息
      +	if v, ok := db.Statement.Context.Value("rid").(string); ok {
      +		rid = v
      +	}
      +
      +	content := fmt.Sprintf("rid=%s", rid)
      +
      +	if db.Statement.SQL.Len() > 0 {
      +		oldSQL := db.Statement.SQL.String()
      +		db.Statement.SQL.Reset()
      +		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
      +		return
      +	}
      +
      +	db.Statement.AddClause(Comment{Content: content})
      +}
      +
      +// initClauses init SQL clause
      +func initClauses(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +	createClause := append(extraClause, db.Callback().Create().Clauses...)
      +	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
      +	queryClause := append(extraClause, db.Callback().Query().Clauses...)
      +	updateClause := append(extraClause, db.Callback().Update().Clauses...)
      +	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
      +	rowClause := append(extraClause, db.Callback().Row().Clauses...)
      +	db.Callback().Create().Clauses = createClause
      +	db.Callback().Delete().Clauses = deleteClause
      +	db.Statement.Callback().Query().Clauses = queryClause
      +	db.Callback().Update().Clauses = updateClause
      +	db.Callback().Raw().Clauses = rawClause
      +	db.Callback().Row().Clauses = rowClause
      +}
      +
      +
      +main.go
      +package main
      +
      +import (
      +    "context"
      +    "plugins"
      +
      +    gorm "gorm.io/gorm"
      +    "github.com/google/uuid"
      +)
      +
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +
      +    db.Use(plugins.NewCommentClausePlugin())
      +
      +    db.Create(&Product{Code: "D42", Price: 100})
      +
      +    // 传入context,指定rid
      +    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
      +    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
      +}
      +

      阻塞了两天的问题,终于解决了!😁😁😁

      +

      how gorm generates sql

      +]]>
      +
      + + 源码分析:GORM是如何生成sql的 + https://liudon.com/posts/how-gorm-generates-sql/ + Thu, 18 Apr 2024 21:14:24 +0800 + https://liudon.com/posts/how-gorm-generates-sql/ + <p>在<code>gorm</code>下实现<a href="https://google.github.io/sqlcommenter/">sqlcommenter</a>过程中,遇到一些问题,顺便把<code>gorm</code>整个流程梳理了一遍,整理记录一下。</p> +<p>gorm使用示例</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>package main +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>import ( +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/driver/mysql&#34;</span> +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;gorm.io/gorm&#34;</span> +</span></span><span style="display:flex;"><span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>type Product struct { +</span></span><span style="display:flex;"><span> gorm<span style="color:#f92672">.</span>Model +</span></span><span style="display:flex;"><span> Code string +</span></span><span style="display:flex;"><span> Price uint +</span></span><span style="display:flex;"><span>} +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> main() { +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">参考</span> https:<span style="color:#f92672">//</span>github<span style="color:#f92672">.</span>com<span style="color:#f92672">/</span>go<span style="color:#f92672">-</span>sql<span style="color:#f92672">-</span>driver<span style="color:#f92672">/</span>mysql<span style="color:#75715e">#dsn-data-source-name 获取详情</span> +</span></span><span style="display:flex;"><span> dsn :<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&amp;parseTime=True&amp;loc=Local&#34;</span> +</span></span><span style="display:flex;"><span> db, err :<span style="color:#f92672">=</span> gorm<span style="color:#f92672">.</span>Open(mysql<span style="color:#f92672">.</span>Open(dsn), <span style="color:#f92672">&amp;</span>gorm<span style="color:#f92672">.</span>Config{}) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">var</span> product Product +</span></span><span style="display:flex;"><span> db<span style="color:#f92672">.</span>First(<span style="color:#f92672">&amp;</span>product, <span style="color:#ae81ff">1</span>) <span style="color:#f92672">//</span> <span style="color:#960050;background-color:#1e0010">根据整型主键查找</span> +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>我们以<code>First</code>查询为例,看一下是怎么转成具体sql的。</p> + gorm下实现sqlcommenter过程中,遇到一些问题,顺便把gorm整个流程梳理了一遍,整理记录一下。

      +

      gorm使用示例

      +
      package main
      +
      +import (
      +  "gorm.io/driver/mysql"
      +  "gorm.io/gorm"
      +)
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
      +  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +  
      +  var product Product
      +  db.First(&product, 1) // 根据整型主键查找
      +}
      +

      我们以First查询为例,看一下是怎么转成具体sql的。

      +

      finisher_api.go文件,声明了First方法。

      +
      // First finds the first record ordered by primary key, matching given conditions conds
      +func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
      +	// 注册Order类型的Clause
      +	tx = db.Limit(1).Order(clause.OrderByColumn{
      +		Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
      +	})
      +	// 这里如果有指定条件的话,注册一个Where类型的Clause
      +	if len(conds) > 0 {
      +		if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
      +			tx.Statement.AddClause(clause.Where{Exprs: exprs})
      +		}
      +	}
      +	tx.Statement.RaiseErrorOnNotFound = true
      +	tx.Statement.Dest = dest
      +	return tx.callbacks.Query().Execute(tx)
      +}
      +

      gorm.go文件,可以找到tx.callbacks定义。

      +
      type Config struct {
      +	...
      +
      +	callbacks  *callbacks
      +	cacheStore *sync.Map
      +}
      +

      callbacks.go

      +
      // callbacks gorm callbacks manager
      +type callbacks struct {
      +	processors map[string]*processor
      +}
      +
      +type processor struct {
      +	db        *DB
      +	Clauses   []string
      +	fns       []func(*DB)
      +	callbacks []*callback
      +}
      +
      +type callback struct {
      +	name      string
      +	before    string
      +	after     string
      +	remove    bool
      +	replace   bool
      +	match     func(*DB) bool
      +	handler   func(*DB)
      +	processor *processor
      +}
      +
      +// 返回query类型的processor
      +func (cs *callbacks) Query() *processor {
      +	return cs.processors["query"]
      +}
      +
      +func (p *processor) Execute(db *DB) *DB {
      +	// call scopes
      +	for len(db.Statement.scopes) > 0 {
      +		db = db.executeScopes()
      +	}
      +
      +	var (
      +		curTime           = time.Now()
      +		stmt              = db.Statement
      +		resetBuildClauses bool
      +	)
      +
      +	// 注意这里的stmt.BuildClauses,后面会用到这个信息
      +	if len(stmt.BuildClauses) == 0 {
      +		stmt.BuildClauses = p.Clauses
      +		resetBuildClauses = true
      +	}
      +
      +	if optimizer, ok := db.Statement.Dest.(StatementModifier); ok {
      +		optimizer.ModifyStatement(stmt)
      +	}
      +
      +	// assign model values
      +	if stmt.Model == nil {
      +		stmt.Model = stmt.Dest
      +	} else if stmt.Dest == nil {
      +		stmt.Dest = stmt.Model
      +	}
      +
      +	// parse model values
      +	if stmt.Model != nil {
      +		if err := stmt.Parse(stmt.Model); err != nil && (!errors.Is(err, schema.ErrUnsupportedDataType) || (stmt.Table == "" && stmt.TableExpr == nil && stmt.SQL.Len() == 0)) {
      +			if errors.Is(err, schema.ErrUnsupportedDataType) && stmt.Table == "" && stmt.TableExpr == nil {
      +				db.AddError(fmt.Errorf("%w: Table not set, please set it like: db.Model(&user) or db.Table(\"users\")", err))
      +			} else {
      +				db.AddError(err)
      +			}
      +		}
      +	}
      +
      +	// assign stmt.ReflectValue
      +	if stmt.Dest != nil {
      +		stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
      +		for stmt.ReflectValue.Kind() == reflect.Ptr {
      +			if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
      +				stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
      +			}
      +
      +			stmt.ReflectValue = stmt.ReflectValue.Elem()
      +		}
      +		if !stmt.ReflectValue.IsValid() {
      +			db.AddError(ErrInvalidValue)
      +		}
      +	}
      +
      +	// 根据优先级执行不同callback的回调方法
      +	for _, f := range p.fns {
      +		f(db)
      +	}
      +
      +	if stmt.SQL.Len() > 0 {
      +		db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
      +			sql, vars := stmt.SQL.String(), stmt.Vars
      +			if filter, ok := db.Logger.(ParamsFilter); ok {
      +				sql, vars = filter.ParamsFilter(stmt.Context, stmt.SQL.String(), stmt.Vars...)
      +			}
      +			return db.Dialector.Explain(sql, vars...), db.RowsAffected
      +		}, db.Error)
      +	}
      +
      +	if !stmt.DB.DryRun {
      +		stmt.SQL.Reset()
      +		stmt.Vars = nil
      +	}
      +
      +	if resetBuildClauses {
      +		stmt.BuildClauses = nil
      +	}
      +
      +	return db
      +}
      +

      接下来,我们来看一下内置的callback是如何注册的。

      +

      mysql.go

      +
      var (
      +	// CreateClauses create clauses
      +	CreateClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	// QueryClauses query clauses
      +	QueryClauses = []string{}
      +	// UpdateClauses update clauses
      +	UpdateClauses = []string{"UPDATE", "SET", "WHERE", "ORDER BY", "LIMIT"}
      +	// DeleteClauses delete clauses
      +	DeleteClauses = []string{"DELETE", "FROM", "WHERE", "ORDER BY", "LIMIT"}
      +
      +	defaultDatetimePrecision = 3
      +)
      +
      +...
      +
      +func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
      +	if dialector.DriverName == "" {
      +		dialector.DriverName = "mysql"
      +	}
      +
      +	if dialector.DefaultDatetimePrecision == nil {
      +		dialector.DefaultDatetimePrecision = &defaultDatetimePrecision
      +	}
      +
      +	if dialector.Conn != nil {
      +		db.ConnPool = dialector.Conn
      +	} else {
      +		db.ConnPool, err = sql.Open(dialector.DriverName, dialector.DSN)
      +		if err != nil {
      +			return err
      +		}
      +	}
      +
      +	withReturning := false
      +	if !dialector.Config.SkipInitializeWithVersion {
      +		err = db.ConnPool.QueryRowContext(context.Background(), "SELECT VERSION()").Scan(&dialector.ServerVersion)
      +		if err != nil {
      +			return err
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "MariaDB") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportNullAsDefaultValue = true
      +			withReturning = checkVersion(dialector.ServerVersion, "10.5")
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.6.") {
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.7.") {
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		} else if strings.HasPrefix(dialector.ServerVersion, "5.") {
      +			dialector.Config.DisableDatetimePrecision = true
      +			dialector.Config.DontSupportRenameIndex = true
      +			dialector.Config.DontSupportRenameColumn = true
      +			dialector.Config.DontSupportForShareClause = true
      +			dialector.Config.DontSupportDropConstraint = true
      +		}
      +
      +		if strings.Contains(dialector.ServerVersion, "TiDB") {
      +			dialector.Config.DontSupportRenameColumnUnique = true
      +		}
      +	}
      +
      +	// register callbacks
      +	callbackConfig := &callbacks.Config{
      +		CreateClauses: CreateClauses,
      +		QueryClauses:  QueryClauses,
      +		UpdateClauses: UpdateClauses,
      +		DeleteClauses: DeleteClauses,
      +	}
      +
      +	if !dialector.Config.DisableWithReturning && withReturning {
      +		if !utils.Contains(callbackConfig.CreateClauses, "RETURNING") {
      +			callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.UpdateClauses, "RETURNING") {
      +			callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
      +		}
      +
      +		if !utils.Contains(callbackConfig.DeleteClauses, "RETURNING") {
      +			callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
      +		}
      +	}
      +
      +	// 注册默认callback
      +	callbacks.RegisterDefaultCallbacks(db, callbackConfig)
      +
      +	for k, v := range dialector.ClauseBuilders() {
      +		db.ClauseBuilders[k] = v
      +	}
      +	return
      +}
      +

      callbacks.go

      +
      var (
      +	createClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
      +	queryClauses  = []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"}
      +	updateClauses = []string{"UPDATE", "SET", "WHERE"}
      +	deleteClauses = []string{"DELETE", "FROM", "WHERE"}
      +)
      +
      +type Config struct {
      +	LastInsertIDReversed bool
      +	CreateClauses        []string
      +	QueryClauses         []string
      +	UpdateClauses        []string
      +	DeleteClauses        []string
      +}
      +
      +func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
      +	enableTransaction := func(db *gorm.DB) bool {
      +		return !db.SkipDefaultTransaction
      +	}
      +
      +	if len(config.CreateClauses) == 0 {
      +		config.CreateClauses = createClauses
      +	}
      +	if len(config.QueryClauses) == 0 {
      +		config.QueryClauses = queryClauses
      +	}
      +	if len(config.DeleteClauses) == 0 {
      +		config.DeleteClauses = deleteClauses
      +	}
      +	if len(config.UpdateClauses) == 0 {
      +		config.UpdateClauses = updateClauses
      +	}
      +
      +    // 注册不同类型的callback
      +	createCallback := db.Callback().Create()
      +	createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	createCallback.Register("gorm:before_create", BeforeCreate)
      +	createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
      +	createCallback.Register("gorm:create", Create(config))
      +	createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
      +	createCallback.Register("gorm:after_create", AfterCreate)
      +	createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	createCallback.Clauses = config.CreateClauses
      +
      +	queryCallback := db.Callback().Query()
      +	queryCallback.Register("gorm:query", Query)
      +	queryCallback.Register("gorm:preload", Preload)
      +	queryCallback.Register("gorm:after_query", AfterQuery)
      +	queryCallback.Clauses = config.QueryClauses
      +
      +	deleteCallback := db.Callback().Delete()
      +	deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	deleteCallback.Register("gorm:before_delete", BeforeDelete)
      +	deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
      +	deleteCallback.Register("gorm:delete", Delete(config))
      +	deleteCallback.Register("gorm:after_delete", AfterDelete)
      +	deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	deleteCallback.Clauses = config.DeleteClauses
      +
      +	updateCallback := db.Callback().Update()
      +	updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
      +	updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
      +	updateCallback.Register("gorm:before_update", BeforeUpdate)
      +	updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
      +	updateCallback.Register("gorm:update", Update(config))
      +	updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
      +	updateCallback.Register("gorm:after_update", AfterUpdate)
      +	updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
      +	updateCallback.Clauses = config.UpdateClauses
      +
      +	rowCallback := db.Callback().Row()
      +	rowCallback.Register("gorm:row", RowQuery)
      +	rowCallback.Clauses = config.QueryClauses
      +
      +	rawCallback := db.Callback().Raw()
      +	rawCallback.Register("gorm:raw", RawExec)
      +	rawCallback.Clauses = config.QueryClauses
      +}
      +

      到这里,默认callback就注册完成了,但是是如何转成对应sql的呢?

      +

      别急,我们继续往下看。

      +

      RegisterDefaultCallbacks方法里注册了一个gorm:query类型的callback,对应的回调方法为Query

      +

      query.go

      +
      func Query(db *gorm.DB) {
      +	if db.Error == nil {
      +		// 调用BuildQuerySQL方法
      +		BuildQuerySQL(db)
      +
      +		if !db.DryRun && db.Error == nil {
      +			rows, err := db.Statement.ConnPool.QueryContext(db.Statement.Context, db.Statement.SQL.String(), db.Statement.Vars...)
      +			if err != nil {
      +				db.AddError(err)
      +				return
      +			}
      +			defer func() {
      +				db.AddError(rows.Close())
      +			}()
      +			gorm.Scan(rows, db, 0)
      +		}
      +	}
      +}
      +
      +func BuildQuerySQL(db *gorm.DB) {
      +	if db.Statement.Schema != nil {
      +		for _, c := range db.Statement.Schema.QueryClauses {
      +			db.Statement.AddClause(c)
      +		}
      +	}
      +
      +	if db.Statement.SQL.Len() == 0 {
      +		db.Statement.SQL.Grow(100)
      +		clauseSelect := clause.Select{Distinct: db.Statement.Distinct}
      +
      +		if db.Statement.ReflectValue.Kind() == reflect.Struct && db.Statement.ReflectValue.Type() == db.Statement.Schema.ModelType {
      +			var conds []clause.Expression
      +			for _, primaryField := range db.Statement.Schema.PrimaryFields {
      +				if v, isZero := primaryField.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {
      +					conds = append(conds, clause.Eq{Column: clause.Column{Table: db.Statement.Table, Name: primaryField.DBName}, Value: v})
      +				}
      +			}
      +
      +			if len(conds) > 0 {
      +				db.Statement.AddClause(clause.Where{Exprs: conds})
      +			}
      +		}
      +
      +		if len(db.Statement.Selects) > 0 {
      +			clauseSelect.Columns = make([]clause.Column, len(db.Statement.Selects))
      +			for idx, name := range db.Statement.Selects {
      +				if db.Statement.Schema == nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				} else if f := db.Statement.Schema.LookUpField(name); f != nil {
      +					clauseSelect.Columns[idx] = clause.Column{Name: f.DBName}
      +				} else {
      +					clauseSelect.Columns[idx] = clause.Column{Name: name, Raw: true}
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && len(db.Statement.Omits) > 0 {
      +			selectColumns, _ := db.Statement.SelectAndOmitColumns(false, false)
      +			clauseSelect.Columns = make([]clause.Column, 0, len(db.Statement.Schema.DBNames))
      +			for _, dbName := range db.Statement.Schema.DBNames {
      +				if v, ok := selectColumns[dbName]; (ok && v) || !ok {
      +					clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{Table: db.Statement.Table, Name: dbName})
      +				}
      +			}
      +		} else if db.Statement.Schema != nil && db.Statement.ReflectValue.IsValid() {
      +			queryFields := db.QueryFields
      +			if !queryFields {
      +				switch db.Statement.ReflectValue.Kind() {
      +				case reflect.Struct:
      +					queryFields = db.Statement.ReflectValue.Type() != db.Statement.Schema.ModelType
      +				case reflect.Slice:
      +					queryFields = db.Statement.ReflectValue.Type().Elem() != db.Statement.Schema.ModelType
      +				}
      +			}
      +
      +			if queryFields {
      +				stmt := gorm.Statement{DB: db}
      +				// smaller struct
      +				if err := stmt.Parse(db.Statement.Dest); err == nil && (db.QueryFields || stmt.Schema.ModelType != db.Statement.Schema.ModelType) {
      +					clauseSelect.Columns = make([]clause.Column, len(stmt.Schema.DBNames))
      +
      +					for idx, dbName := range stmt.Schema.DBNames {
      +						clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +					}
      +				}
      +			}
      +		}
      +
      +		// inline joins
      +		fromClause := clause.From{}
      +		if v, ok := db.Statement.Clauses["FROM"].Expression.(clause.From); ok {
      +			fromClause = v
      +		}
      +
      +		if len(db.Statement.Joins) != 0 || len(fromClause.Joins) != 0 {
      +			if len(db.Statement.Selects) == 0 && len(db.Statement.Omits) == 0 && db.Statement.Schema != nil {
      +				clauseSelect.Columns = make([]clause.Column, len(db.Statement.Schema.DBNames))
      +				for idx, dbName := range db.Statement.Schema.DBNames {
      +					clauseSelect.Columns[idx] = clause.Column{Table: db.Statement.Table, Name: dbName}
      +				}
      +			}
      +
      +			specifiedRelationsName := make(map[string]interface{})
      +			for _, join := range db.Statement.Joins {
      +				if db.Statement.Schema != nil {
      +					var isRelations bool // is relations or raw sql
      +					var relations []*schema.Relationship
      +					relation, ok := db.Statement.Schema.Relationships.Relations[join.Name]
      +					if ok {
      +						isRelations = true
      +						relations = append(relations, relation)
      +					} else {
      +						// handle nested join like "Manager.Company"
      +						nestedJoinNames := strings.Split(join.Name, ".")
      +						if len(nestedJoinNames) > 1 {
      +							isNestedJoin := true
      +							gussNestedRelations := make([]*schema.Relationship, 0, len(nestedJoinNames))
      +							currentRelations := db.Statement.Schema.Relationships.Relations
      +							for _, relname := range nestedJoinNames {
      +								// incomplete match, only treated as raw sql
      +								if relation, ok = currentRelations[relname]; ok {
      +									gussNestedRelations = append(gussNestedRelations, relation)
      +									currentRelations = relation.FieldSchema.Relationships.Relations
      +								} else {
      +									isNestedJoin = false
      +									break
      +								}
      +							}
      +
      +							if isNestedJoin {
      +								isRelations = true
      +								relations = gussNestedRelations
      +							}
      +						}
      +					}
      +
      +					if isRelations {
      +						genJoinClause := func(joinType clause.JoinType, parentTableName string, relation *schema.Relationship) clause.Join {
      +							tableAliasName := relation.Name
      +							if parentTableName != clause.CurrentTable {
      +								tableAliasName = utils.NestedRelationName(parentTableName, tableAliasName)
      +							}
      +
      +							columnStmt := gorm.Statement{
      +								Table: tableAliasName, DB: db, Schema: relation.FieldSchema,
      +								Selects: join.Selects, Omits: join.Omits,
      +							}
      +
      +							selectColumns, restricted := columnStmt.SelectAndOmitColumns(false, false)
      +							for _, s := range relation.FieldSchema.DBNames {
      +								if v, ok := selectColumns[s]; (ok && v) || (!ok && !restricted) {
      +									clauseSelect.Columns = append(clauseSelect.Columns, clause.Column{
      +										Table: tableAliasName,
      +										Name:  s,
      +										Alias: utils.NestedRelationName(tableAliasName, s),
      +									})
      +								}
      +							}
      +
      +							exprs := make([]clause.Expression, len(relation.References))
      +							for idx, ref := range relation.References {
      +								if ref.OwnPrimaryKey {
      +									exprs[idx] = clause.Eq{
      +										Column: clause.Column{Table: parentTableName, Name: ref.PrimaryKey.DBName},
      +										Value:  clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +									}
      +								} else {
      +									if ref.PrimaryValue == "" {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: parentTableName, Name: ref.ForeignKey.DBName},
      +											Value:  clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName},
      +										}
      +									} else {
      +										exprs[idx] = clause.Eq{
      +											Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName},
      +											Value:  ref.PrimaryValue,
      +										}
      +									}
      +								}
      +							}
      +
      +							{
      +								onStmt := gorm.Statement{Table: tableAliasName, DB: db, Clauses: map[string]clause.Clause{}}
      +								for _, c := range relation.FieldSchema.QueryClauses {
      +									onStmt.AddClause(c)
      +								}
      +
      +								if join.On != nil {
      +									onStmt.AddClause(join.On)
      +								}
      +
      +								if cs, ok := onStmt.Clauses["WHERE"]; ok {
      +									if where, ok := cs.Expression.(clause.Where); ok {
      +										where.Build(&onStmt)
      +
      +										if onSQL := onStmt.SQL.String(); onSQL != "" {
      +											vars := onStmt.Vars
      +											for idx, v := range vars {
      +												bindvar := strings.Builder{}
      +												onStmt.Vars = vars[0 : idx+1]
      +												db.Dialector.BindVarTo(&bindvar, &onStmt, v)
      +												onSQL = strings.Replace(onSQL, bindvar.String(), "?", 1)
      +											}
      +
      +											exprs = append(exprs, clause.Expr{SQL: onSQL, Vars: vars})
      +										}
      +									}
      +								}
      +							}
      +
      +							return clause.Join{
      +								Type:  joinType,
      +								Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName},
      +								ON:    clause.Where{Exprs: exprs},
      +							}
      +						}
      +
      +						parentTableName := clause.CurrentTable
      +						for _, rel := range relations {
      +							// joins table alias like "Manager, Company, Manager__Company"
      +							nestedAlias := utils.NestedRelationName(parentTableName, rel.Name)
      +							if _, ok := specifiedRelationsName[nestedAlias]; !ok {
      +								fromClause.Joins = append(fromClause.Joins, genJoinClause(join.JoinType, parentTableName, rel))
      +								specifiedRelationsName[nestedAlias] = nil
      +							}
      +
      +							if parentTableName != clause.CurrentTable {
      +								parentTableName = utils.NestedRelationName(parentTableName, rel.Name)
      +							} else {
      +								parentTableName = rel.Name
      +							}
      +						}
      +					} else {
      +						fromClause.Joins = append(fromClause.Joins, clause.Join{
      +							Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +						})
      +					}
      +				} else {
      +					fromClause.Joins = append(fromClause.Joins, clause.Join{
      +						Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds},
      +					})
      +				}
      +			}
      +
      +			db.Statement.AddClause(fromClause)
      +		} else {
      +			db.Statement.AddClauseIfNotExists(clause.From{})
      +		}
      +
      +		db.Statement.AddClauseIfNotExists(clauseSelect)
      +
      +		// db.Statement.BuildClauses眼熟吗?还记得前面的stmt.BuildClauses吗
      +		db.Statement.Build(db.Statement.BuildClauses...)
      +	}
      +}
      +

      重头戏终于来了,Query方法里调用了BuildQuerySQl,看名字也能猜到这里就是生成sql了,这里最终调用了db.Statement.Build方法。

      +

      statement.go

      +
      // Build build sql with clauses names
      +func (stmt *Statement) Build(clauses ...string) {
      +	var firstClauseWritten bool
      +
      +	for _, name := range clauses {
      +		if c, ok := stmt.Clauses[name]; ok {
      +			if firstClauseWritten {
      +				stmt.WriteByte(' ')
      +			}
      +
      +			firstClauseWritten = true
      +			if b, ok := stmt.DB.ClauseBuilders[name]; ok {
      +				b(c, stmt)
      +			} else {
      +				c.Build(stmt)
      +			}
      +		}
      +	}
      +}
      +

      这里会根据statementBuildCluauses属性,执行ClauseBuild方法。

      +

      clause.go

      +
      // ClauseBuilder clause builder, allows to customize how to build clause
      +type ClauseBuilder func(Clause, Builder)
      +
      +type Writer interface {
      +	WriteByte(byte) error
      +	WriteString(string) (int, error)
      +}
      +
      +// Builder builder interface
      +type Builder interface {
      +	Writer
      +	WriteQuoted(field interface{})
      +	AddVar(Writer, ...interface{})
      +	AddError(error) error
      +}
      +
      +// Clause
      +type Clause struct {
      +	Name                string // WHERE
      +	BeforeExpression    Expression
      +	AfterNameExpression Expression
      +	AfterExpression     Expression
      +	Expression          Expression
      +	Builder             ClauseBuilder
      +}
      +
      +// Build build clause
      +func (c Clause) Build(builder Builder) {
      +	if c.Builder != nil {
      +		c.Builder(c, builder)
      +	} else if c.Expression != nil {
      +		if c.BeforeExpression != nil {
      +			c.BeforeExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.Name != "" {
      +			builder.WriteString(c.Name)
      +			builder.WriteByte(' ')
      +		}
      +
      +		if c.AfterNameExpression != nil {
      +			c.AfterNameExpression.Build(builder)
      +			builder.WriteByte(' ')
      +		}
      +
      +		c.Expression.Build(builder)
      +
      +		if c.AfterExpression != nil {
      +			builder.WriteByte(' ')
      +			c.AfterExpression.Build(builder)
      +		}
      +	}
      +}
      +

      这里会执行对应Clause的Build方法。

      +
      // Select select attrs when querying, updating, creating
      +type Select struct {
      +	Distinct   bool
      +	Columns    []Column
      +	Expression Expression
      +}
      +
      +func (s Select) Name() string {
      +	return "SELECT"
      +}
      +
      +func (s Select) Build(builder Builder) {
      +	if len(s.Columns) > 0 {
      +		if s.Distinct {
      +			builder.WriteString("DISTINCT ")
      +		}
      +
      +		for idx, column := range s.Columns {
      +			if idx > 0 {
      +				builder.WriteByte(',')
      +			}
      +			builder.WriteQuoted(column)
      +		}
      +	} else {
      +		builder.WriteByte('*')
      +	}
      +}
      +
      +func (s Select) MergeClause(clause *Clause) {
      +	if s.Expression != nil {
      +		if s.Distinct {
      +			if expr, ok := s.Expression.(Expr); ok {
      +				expr.SQL = "DISTINCT " + expr.SQL
      +				clause.Expression = expr
      +				return
      +			}
      +		}
      +
      +		clause.Expression = s.Expression
      +	} else {
      +		clause.Expression = s
      +	}
      +}
      +

      这是Select类型的Clause定义,是不是一下就清楚了。

      +

      gorm通过callback里注册Clause,在Clause里实现了sql拼接操作。

      +

      看了几回源码,这次总算是搞清楚了。

      +]]>
      +
      +
      +
      diff --git a/tags/gorm/page/1/index.html b/tags/gorm/page/1/index.html new file mode 100644 index 000000000..04eb63934 --- /dev/null +++ b/tags/gorm/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/gorm/ + \ No newline at end of file diff --git a/tags/hugo/index.html b/tags/hugo/index.html new file mode 100644 index 000000000..4041870e1 --- /dev/null +++ b/tags/hugo/index.html @@ -0,0 +1,26 @@ +Hugo | 流动 +

      使用Hugo实现响应式和优化的图片

      继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。 +问题 在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高。 +经过一段运行后,发现这里有一个弊端。 +Run hugo --gc --minify --cleanDestinationDir Start building sites … hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer 随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。 +...

      2023-12-10 · 5 min · 2021 words · Liudon

      使用Google Indexing API加速博客收录

      对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。 +今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。 +...

      2023-10-27 · 2 min · 635 words · Liudon

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon

      修正Hugo的JSON Feed格式

      问题背景 前几天在Planet里follow自己的web3博客,遇到下面的错误。 +经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。 +因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。 +[ { "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n", "permalink": "https://dvel.me/posts/chd-quiz-answer/", "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!", "title": "CHD 油猴脚本:每日签到自动答题" }, ... ] 下面是一个JSON Feed的示例,详细规范见jsonfeed.org。 +...

      2023-03-25 · 3 min · 1451 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      将博客部署到Cloudflare Pages

      目前博客是部署到了Github Pages上,具体实现见博客架构说明。 +缘由 Github Pages部署有一个问题,就是不支持HSTS。 +HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。 +...

      2022-07-29 · 2 min · 644 words · Liudon
      \ No newline at end of file diff --git a/tags/hugo/index.xml b/tags/hugo/index.xml new file mode 100644 index 000000000..bae96e883 --- /dev/null +++ b/tags/hugo/index.xml @@ -0,0 +1,1111 @@ + + + + Hugo on 流动 + https://liudon.com/tags/hugo/ + Recent content in Hugo on 流动 + Hugo -- 0.134.3 + zh-cn + Sun, 10 Dec 2023 08:29:05 +0800 + + + 使用Hugo实现响应式和优化的图片 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + Sun, 10 Dec 2023 08:29:05 +0800 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + <p>继续我们的<a href="../../tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/">博客优化之旅</a>,本篇内容我们将介绍如何使用<code>Hugo</code>实现响应式和优化的图片。</p> +<h4 id="问题">问题</h4> +<p>在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>。</p> +<p>经过一段运行后,发现这里有一个弊端。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Run hugo --gc --minify --cleanDestinationDir +</span></span><span style="display:flex;"><span>Start building sites … +</span></span><span style="display:flex;"><span>hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer +</span></span></code></pre></div><p>随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。</p> + 继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

      +

      问题

      +

      在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

      +

      经过一段运行后,发现这里有一个弊端。

      +
      Run hugo --gc --minify --cleanDestinationDir
      +Start building sites … 
      +hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio
      +
      +ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
      +ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer
      +

      随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

      +

      失败的时候,需要手动重跑构建,自动化发布卡壳了。

      +

      优化

      +

      经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

      +
      +

      Image processing

      +

      Resize, crop, rotate, filter, and convert images.

      +

      https://gohugo.io/content-management/image-processing/

      +
      +

      下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

      +

      image processing需要用到Page bundles,所以文章目录结构需要调整, +将一篇文章的资源(md文件,图片等)放在一个目录下。

      +
      content/
      +├── posts
      +│   ├── my-post
      +│   │   ├── content1.md
      +│   │   ├── content2.md
      +│   │   ├── image1.jpg
      +│   │   ├── image2.png
      +│   │   └── index.md
      +│   └── my-other-post
      +│       └── index.md
      +

      目录结构调整完毕后,接下来修改图片显示文件代码。

      +

      这里需要生成webp格式图片,所以需要使用hugo的extended版本

      +

      PagerMod主题涉及到图片显示的一共三个文件:

      +
        +
      • +

        _default/_markup/render-image.html,对应markdown图片语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +{{ with $src := .Page.Resources.GetMatch .Destination }}
        +    {{- if $responsiveImages -}}
        +        <picture>
        +            /*只有使用了hugo扩展版本的,才生成webp格式图片*/
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +            <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +        </picture>
        +    {{- else }}
        +        <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +    {{- end }}
        +{{ end }}
        +
      • +
      • +

        partials/cover.html,对应文章封面解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    /*封面响应式图片配置开关,默认为true*/
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- if $responsiveImages -}}
        +        <picture>
        +        {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
        +        <source type="image/webp" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +            {{- end -}}
        +        {{- end -}}" sizes="{{ $dataSzes }}" />
        +        {{- end -}}
        +        <source type="{{ $cover.MediaType.Type }}" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if ge $cover.Width . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}
        +        {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
        +
        +        <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        </picture>
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      • +
      • +

        shortcodes/figure.html,对应文章内figure语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{ $src := .Get "src" }}
        +{{ $align := .Get "align" }}
        +{{ $alt := .Get "alt" }}
        +{{ $caption := .Get "caption" }}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
        +        {{- if eq (.Get "align") "center" }}align-center {{ end }}
        +        {{- with .Get "class" }}{{ . }}{{- end }}"
        +{{- end -}}>
        +    {{- if .Get "link" -}}
        +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        +    {{- end }}
        +    {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        +    <picture>
        +        {{- if $responsiveImages -}}
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +        {{- end }}
        +        <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
        +        {{- if or ($alt) ($caption) }}
        +        alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
        +        {{- end -}}
        +        {{- with $src.Width -}} width="{{ . }}"{{- end -}}
        +        {{- with $src.Height -}} height="{{ . }}"{{- end -}}
        +        /> <!-- Closing img tag -->
        +    </picture>
        +    {{ end }}
        +    {{- if .Get "link" }}</a>{{ end -}}
        +    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        +        <figcaption>
        +            {{ with (.Get "title") -}}
        +                {{ . }}
        +            {{- end -}}
        +            {{- if or (.Get "caption") (.Get "attr") -}}<p>
        +                {{- .Get "caption" | markdownify -}}
        +                {{- with .Get "attrlink" }}
        +                    <a href="{{ . }}">
        +                {{- end -}}
        +                {{- .Get "attr" | markdownify -}}
        +                {{- if .Get "attrlink" }}</a>{{ end }}</p>
        +            {{- end }}
        +        </figcaption>
        +    {{- end }}
        +</figure>
        +
      • +
      +

      使用效果

      +

      正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

      +
        +
      • markdown图片显示
      • +
      +

      + +render-image + + +

      +
        +
      • figure短代码显示
      • +
      +

      + +figure + + +

      +
        +
      • 封面显示
      • +
      +

      + +cover + + +

      +

      提示:

      +

      随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

      +
      Start building sites  
      +hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
      +Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
      +Built in 22356 ms
      +

      可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

      +
      buildDrafts: false
      +buildFuture: false
      +buildExpired: false
      +
      +timeout: 60s // 调大此处的时间即可
      +

      终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。

      +]]>
      +
      + + 使用Google Indexing API加速博客收录 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/posts/how-to-use-google-indexing-api-to-speed-up-blog-indexing/ + <p>对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。</p> +<p>今天,我们就来介绍一种利用<code>Google Indexing API</code>接口,通过<code>Github Actions</code>实现部署时通知<code>Google</code>抓取页面内容。</p> + 对于一个新站点来说,总是想着能让搜索引擎快点收录网站内容。

      +

      今天,我们就来介绍一种利用Google Indexing API接口,通过Github Actions实现部署时通知Google抓取页面内容。

      +

      操作步骤:

      +
        +
      1. +

        申请Google API凭证

        +

        访问Google Cloud控制台,如果没有项目,点击选择项目,然后新建项目。

        +

        + + + + +

        +

        选择对应项目,点击IAM和管理标签,点击服务帐号,选择新建服务帐号。

        +

        + + + + +

        +
        服务帐号名称:自己起个名字即可
        +服务帐号id:不需要修改,自动生成
        +服务角色:Owner
        +

        填写相关信息后,点击完成创建好服务帐号。

        +

        + + + + +

        +

        创建完,默认是没有密钥的,记住账号的邮箱地址,后面要用到。

        +

        点击后面的三个点按钮,选择管理密钥。

        +

        点击添加密钥->新建密钥,选择JSON格式,点击创建会下载一个文件,这里后面会用到。

        +

        回到首页,点击API和服务,点击启用API和服务,搜索框输入Indexing,选择Web Search Indexing API,点击启用即可。

        +
      2. +
      3. +

        将服务账号关联到Google Search Console

        +

        进入Google Search Console控制台,选择你的网站。

        +

        找到设置里的用户和权限,点击添加用户。

        +

        + + + + +

        +
        邮箱地址:填写第一步分配的邮箱地址
        +权限:选择拥有者
        +
      4. +
      5. +

        配置Github Action

        +
          +
        • +

          添加Secret变量,变量key为GOOGLE_INDEXING_API_TOKEN,值为前面下载文件的内容。

          +
        • +
        • +

          编辑workflow编排任务,新增步骤

          +
        • +
        +
        - name: easyindex
        +    run: |
        +      echo '${{ secrets.GOOGLE_INDEXING_API_TOKEN }}' > ./credentials.json
        +
        +      touch ./url.csv
        +      echo "\"notification_type\",\"url\"" >> ./url.csv # Headers line
        +      echo "\"URL_UPDATED\",\"https://liudon.com/\"" >> ./url.csv # ADD URL 这里改为你的博客首页
        +      echo "\"URL_UPDATED\",\"https://liudon.com/sitemap.xml\"" >> ./url.csv # ADD URL 这里改为你的sitemap地址
        +
        +      curl -s -L https://github.com/usk81/easyindex-cli/releases/download/v1.0.6/easyindex-cli_1.0.6_linux_amd64.tar.gz | tar xz
        +      chmod +x ./easyindex-cli
        +      ./easyindex-cli google -d -c ./url.csv
        +

        + + + + +

        +
      6. +
      +]]>
      +
      + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      + + 修正Hugo的JSON Feed格式 + https://liudon.com/posts/fix-hugo-json-feed/ + Sat, 25 Mar 2023 14:11:18 +0800 + https://liudon.com/posts/fix-hugo-json-feed/ + <h4 id="问题背景">问题背景</h4> +<p>前几天在<a href="https://planetics.xyz/">Planet</a>里follow自己的<a href="https://liudon.eth">web3博客</a>,遇到下面的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu11234891129385791741.webp 716w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/fix-hugo-json-feed/202303251415675_hu7691624655723447714.png 716w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202303251415675.png" width="716" height="544" alt="PlanetFeedError" title="" loading="lazy" /> + </picture> + +</p> +<p>经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。</p> +<p>因为我的已经修正没法截图,这里以<a href="https://dvel.me/index.json">dvel的博客</a>举例,格式类似如下。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[ +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> &#34;content&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&amp;#34;}], &amp;#34;temperature&amp;#34;: 0.7 }&amp;#39; ``` 响应格式为: ``` { &amp;#34;id&amp;#34;:&amp;#34;chatcmpl-abc123&amp;#34;, &amp;#34;object&amp;#34;:&amp;#34;chat.completion&amp;#34;, &amp;#34;created&amp;#34;:1677858242, &amp;#34;model&amp;#34;:&amp;#34;gpt-3.5-turbo-0301&amp;#34;, &amp;#34;usage&amp;#34;:{ &amp;#34;prompt_tokens&amp;#34;:13, &amp;#34;completion_tokens&amp;#34;:7, &amp;#34;total_tokens&amp;#34;:20 }, &amp;#34;choices&amp;#34;:[ { &amp;#34;message&amp;#34;:{ &amp;#34;role&amp;#34;:&amp;#34;assistant&amp;#34;, &amp;#34;content&amp;#34;:&amp;#34;\\n\\nThis is a test!&amp;#34; }, &amp;#34;finish_reason&amp;#34;:&amp;#34;stop&amp;#34;, &amp;#34;index&amp;#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n&#34;, +</span></span><span style="display:flex;"><span> &#34;permalink&#34;: &#34;https://dvel.me/posts/chd-quiz-answer/&#34;, +</span></span><span style="display:flex;"><span> &#34;summary&#34;: &#34;用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &amp;gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(1) &amp;gt; td` 选项的获取路径是 `#outer &amp;gt; form &amp;gt; table &amp;gt; tbody &amp;gt; tr:nth-child(2) &amp;gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &amp;#39;Content-Type: application/json&amp;#39; \\ -H &amp;#39;Authorization: Bearer YOUR_API_KEY&amp;#39; \\ -d &amp;#39;{ &amp;#34;model&amp;#34;: &amp;#34;gpt-3.5-turbo&amp;#34;, &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Say this is a test!&#34;, +</span></span><span style="display:flex;"><span> &#34;title&#34;: &#34;CHD 油猴脚本:每日签到自动答题&#34; +</span></span><span style="display:flex;"><span> }, +</span></span><span style="display:flex;"><span> ... +</span></span><span style="display:flex;"><span>] +</span></span></code></pre></div><p>下面是一个<code>JSON Feed</code>的示例,详细规范见<a href="https://www.jsonfeed.org/">jsonfeed.org</a>。</p> + 问题背景 +

      前几天在Planet里follow自己的web3博客,遇到下面的错误。

      +

      + +PlanetFeedError + + +

      +

      经过Livid大佬提醒,说是网站的JSON Feed不是标准格式导致的。

      +

      因为我的已经修正没法截图,这里以dvel的博客举例,格式类似如下。

      +
      [
      +  {
      +    "content": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!&#34;}], &#34;temperature&#34;: 0.7 }&#39; ``` 响应格式为: ``` { &#34;id&#34;:&#34;chatcmpl-abc123&#34;, &#34;object&#34;:&#34;chat.completion&#34;, &#34;created&#34;:1677858242, &#34;model&#34;:&#34;gpt-3.5-turbo-0301&#34;, &#34;usage&#34;:{ &#34;prompt_tokens&#34;:13, &#34;completion_tokens&#34;:7, &#34;total_tokens&#34;:20 }, &#34;choices&#34;:[ { &#34;message&#34;:{ &#34;role&#34;:&#34;assistant&#34;, &#34;content&#34;:&#34;\\n\\nThis is a test!&#34; }, &#34;finish_reason&#34;:&#34;stop&#34;, &#34;index&#34;:0 } ] } ``` 它没有最近的互联网数据,所以还是需要把 API 的使用方式发给它。\n然后它就帮我写好了,我不用复习 JavaScript,不用看油猴脚本的教程和文档,也不用查 @grant 等等标记是干嘛的。\n可以再继续要求它改进一些,比如换个输出位置,优化 prompt,自动选中正确回答,支持单选题和多选题等等。\n效果展示:\n安装: https://greasyfork.org/zh-CN/scripts/461944-chd-quiz-answer\n",
      +    "permalink": "https://dvel.me/posts/chd-quiz-answer/",
      +    "summary": "用 ChatGPT 写一些小脚本真是太方便了。\nGPT-4 发布后试了试,还是蛮不错的,代码是 ChatGPT 生成的。\n几个来回就可以编写一个能正常使用的油猴脚本:\n(略,HTML 代码) 在 https://chdbits.co/bakatest.php 有如上内容。 我要为这个网页编写一个油猴脚本。 通过自动获取 ChatGPT 的 API 来解析此问题的答案,供用户参考。 将内容输出到 `#outer &gt; h1` 的下面,同时输出你提取到的问题内容和答案,以便我看看你是否提取正确。 获取错啦。 问题的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(1) &gt; td` 选项的获取路径是 `#outer &gt; form &gt; table &gt; tbody &gt; tr:nth-child(2) &gt; td` 使用这个 API: ``` curl https://api.openai.com/v1/chat/completions \\ -H &#39;Content-Type: application/json&#39; \\ -H &#39;Authorization: Bearer YOUR_API_KEY&#39; \\ -d &#39;{ &#34;model&#34;: &#34;gpt-3.5-turbo&#34;, &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Say this is a test!",
      +    "title": "CHD 油猴脚本:每日签到自动答题"
      +  },
      +  ...
      +]
      +

      下面是一个JSON Feed的示例,详细规范见jsonfeed.org

      +
      {
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": "My Example Feed",
      +    "home_page_url": "https://example.org/",
      +    "feed_url": "https://example.org/feed.json",
      +    "items": [
      +        {
      +            "id": "2",
      +            "content_text": "This is a second item.",
      +            "url": "https://example.org/second-item"
      +        },
      +        {
      +            "id": "1",
      +            "content_html": "<p>Hello, world!</p>",
      +            "url": "https://example.org/initial-post"
      +        }
      +    ]
      +}
      +

      修复方案

      +
        +
      1. 添加自定义jsonfeed模版文件,路径为layouts/_default/index.jsonfeed.json
      2. +
      +

      文件内容如下:

      +
      {{- $pctx := . -}}
      +{{- if .IsHome -}}{{ $pctx = site }}{{- end -}}
      +{{- $pages := slice -}}
      +{{- if or $.IsHome $.IsSection -}}
      +{{- $pages = $pctx.RegularPages -}}
      +{{- else -}}
      +{{- $pages = $pctx.Pages -}}
      +{{- end -}}
      +{{- $limit := site.Config.Services.RSS.Limit -}}
      +{{- if ge $limit 1 -}}
      +{{- $pages = $pages | first $limit -}}
      +{{- end -}}
      +{{- $title := "" }}
      +{{- if eq .Title .Site.Title }}
      +{{- $title = .Site.Title }}
      +{{- else }}
      +{{- with .Title }}
      +{{- $title = print . " on "}}
      +{{- end }}
      +{{- $title = print $title .Site.Title }}
      +{{- end }}
      +{
      +    "version": "https://jsonfeed.org/version/1.1",
      +    "title": {{ $title | jsonify }},
      +    "home_page_url": {{ .Permalink | jsonify }},
      +    {{- with  .OutputFormats.Get "jsonfeed" }}
      +    "feed_url": {{ .Permalink | jsonify  }},
      +    {{- end }}
      +    {{- if (or .Site.Params.author .Site.Params.author_url) }}
      +    "authors": [{
      +      {{- if .Site.Params.author }}
      +        "name": {{ .Site.Params.author | jsonify }},
      +      {{- end }}
      +      {{- if .Site.Params.author_url }}
      +        "url": {{ .Site.Params.author_url | jsonify }}
      +      {{- end }}
      +    }],
      +    {{- end }}
      +    {{- if $pages }}
      +    "items": [
      +        {{- range $index, $element := $pages }}
      +        {{- with $element }}
      +        {{- if $index }},{{end}} {
      +            "title": {{ .Title | jsonify }},
      +            "id": {{ .Permalink | jsonify }},
      +            "url": {{ .Permalink | jsonify }},
      +            {{- if .Site.Params.showFullTextinJSONFeed }}
      +            "summary": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            "content_html": {{ .Content | jsonify }},
      +            {{- else }}
      +            "content_text": {{ with .Description }}{{ . | jsonify }}{{ else }}{{ .Summary | jsonify }}{{ end -}},
      +            {{- end }}
      +            {{- if .Params.cover.image }}
      +            {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
      +            {{- if $cover }}
      +            "image": {{ (path.Join .RelPermalink $cover) | absURL | jsonify }},
      +            {{- end }}
      +            {{- end }}
      +            "date_published": {{ .Date.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
      +            {{- $tags := slice -}}
      +            {{ with .Params.tags }}
      +            {{ range . }}
      +            {{ $tags = $tags | append (. | jsonify) }}
      +            {{end}}
      +            {{end}}
      +            "tags": [{{ delimit $tags ", " }}]
      +        }
      +        {{- end }}
      +        {{- end }}
      +    ]
      +    {{ end }}
      +}
      +
        +
      1. 开启JSON Feed
      2. +
      +

      配置文件调整如下:

      +
      outputFormats:
      +  jsonfeed: # 添加jsonfeed输出格式
      +    mediaType: application/feed+json
      +    baseName: feed
      +    rel: alternate
      +    isPlainText: true
      +
      +outputs:
      +    home:
      +        - HTML
      +        - RSS
      +        - json # fusejs搜索依赖index.json,不要漏掉这个配置
      +        - jsonfeed # 开启jsonfeed
      +
      +params:
      +    ...
      +
      +    showFullTextinJSONFeed: true # jsonfeed开启全文输出
      +

      参考资料:How to add JSON Feed support to Hugo

      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="../../posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 将博客部署到Cloudflare Pages + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/posts/deploy-blog-to-cloudflare-pages/ + <p>目前博客是部署到了<code>Github Pages</code>上,具体实现见<a href="https://liudon.com/posts/%E5%8D%9A%E5%AE%A2%E6%9E%B6%E6%9E%84%E8%AF%B4%E6%98%8E/">博客架构说明</a>。</p> +<h4 id="缘由">缘由</h4> +<p><code>Github Pages</code>部署有一个问题,就是不支持<code>HSTS</code>。</p> +<blockquote> +<p>HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。</p> + 目前博客是部署到了Github Pages上,具体实现见博客架构说明

      +

      缘由

      +

      Github Pages部署有一个问题,就是不支持HSTS

      +
      +

      HTTP Strict Transport Security(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过 HTTPS 访问当前资源,而不是HTTP。

      +
      +

      + +20220729232638 + + +

      +

      虽然Github Pages提供了Enforce HTTPS的选项,开启后http请求会301跳转到https 请求。

      +

      但是因为返回包缺少Strict-Transport-Security的Header头,导致HSTS校验失败。

      +

      + +20220729234321 + + +

      +

      为了彻底支持HSTS,决定切换到Cloudflare Pages

      +

      部署

      +

      Cloudflare Pages部署非常简单,授权Github仓库权限,配置好分支即可,这里不多介绍。

      +

      Github Pages上,code分支保存原始文件,master分支保存hugo构建后的文件。

      +

      + +20220729234310 + + +

      +

      Cloudflare Pages这里生成分支选择master,同时禁用其他分支的自动构建。

      +

      这样提交代码后,github actions构建文件提交到master分支,然后触发Cloudflare Pages部署。

      +

      这里为什么没有采用Cloudflare Pages的自动构建呢?因为很慢,构建一次要3分钟左右。

      +

      采用拉取master构建好的文件的话,只需要7秒左右。

      +

      + +20220729234651 + + +

      +

      补齐Header头

      +

      部署好后,Cloudflare Pages的返回也是没有Strict-Transport-SecurityHeader头的。

      +

      这里需要通过自定义Header头实现,增加_headers文件,内容如下:

      +
      /*
      +  strict-transport-security: max-age=31536000; includeSubDomains; preload
      +

      + +20220729234942 + + +

      +

      至此HSTS搞定。

      +

      HSTS资料

      +]]>
      +
      +
      +
      diff --git a/tags/hugo/page/1/index.html b/tags/hugo/page/1/index.html new file mode 100644 index 000000000..cc057848c --- /dev/null +++ b/tags/hugo/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/hugo/ + \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 000000000..99698f2f0 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,5 @@ +标签 | 流动 +
      \ No newline at end of file diff --git a/tags/index.xml b/tags/index.xml new file mode 100644 index 000000000..dbaa20109 --- /dev/null +++ b/tags/index.xml @@ -0,0 +1,369 @@ + + + + 标签 on 流动 + https://liudon.com/tags/ + Recent content in 标签 on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 24 Sep 2024 21:30:21 +0800 + + + Github Pages + https://liudon.com/tags/github-pages/ + Tue, 24 Sep 2024 21:30:21 +0800 + https://liudon.com/tags/github-pages/ + + + + Follow + https://liudon.com/tags/follow/ + Tue, 17 Sep 2024 00:53:38 +0800 + https://liudon.com/tags/follow/ + + + + 爬山 + https://liudon.com/tags/%E7%88%AC%E5%B1%B1/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/tags/%E7%88%AC%E5%B1%B1/ + + + + 遛娃 + https://liudon.com/tags/%E9%81%9B%E5%A8%83/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/tags/%E9%81%9B%E5%A8%83/ + + + + Google Adsense + https://liudon.com/tags/google-adsense/ + Mon, 16 Sep 2024 23:18:50 +0800 + https://liudon.com/tags/google-adsense/ + + + + Filebase + https://liudon.com/tags/filebase/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/tags/filebase/ + + + + Github + https://liudon.com/tags/github/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/tags/github/ + + + + Ipfs + https://liudon.com/tags/ipfs/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/tags/ipfs/ + + + + 旅行 + https://liudon.com/tags/%E6%97%85%E8%A1%8C/ + Sat, 31 Aug 2024 21:24:53 +0800 + https://liudon.com/tags/%E6%97%85%E8%A1%8C/ + + + + Go + https://liudon.com/tags/go/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/tags/go/ + + + + Protoc + https://liudon.com/tags/protoc/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/tags/protoc/ + + + + Cloudflare + https://liudon.com/tags/cloudflare/ + Wed, 22 May 2024 21:20:20 +0800 + https://liudon.com/tags/cloudflare/ + + + + Cors + https://liudon.com/tags/cors/ + Fri, 17 May 2024 20:13:57 +0800 + https://liudon.com/tags/cors/ + + + + Gorm + https://liudon.com/tags/gorm/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/tags/gorm/ + + + + Sqlcommenter + https://liudon.com/tags/sqlcommenter/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/tags/sqlcommenter/ + + + + 博客优化 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/ + Wed, 21 Feb 2024 20:25:49 +0800 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/ + + + + 年终总结 + https://liudon.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + Thu, 04 Jan 2024 18:41:20 +0800 + https://liudon.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + + + + Hugo + https://liudon.com/tags/hugo/ + Sun, 10 Dec 2023 08:29:05 +0800 + https://liudon.com/tags/hugo/ + + + + Google Analytics + https://liudon.com/tags/google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/tags/google-analytics/ + + + + Google + https://liudon.com/tags/google/ + Fri, 27 Oct 2023 19:32:24 +0800 + https://liudon.com/tags/google/ + + + + Netlify + https://liudon.com/tags/netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/tags/netlify/ + + + + Twikoo + https://liudon.com/tags/twikoo/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/tags/twikoo/ + + + + Vercel + https://liudon.com/tags/vercel/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/tags/vercel/ + + + + Weibo + https://liudon.com/tags/weibo/ + Sat, 07 Oct 2023 13:17:57 +0800 + https://liudon.com/tags/weibo/ + + + + Golang + https://liudon.com/tags/golang/ + Thu, 17 Aug 2023 09:20:50 +0800 + https://liudon.com/tags/golang/ + + + + 个人技能 + https://liudon.com/tags/%E4%B8%AA%E4%BA%BA%E6%8A%80%E8%83%BD/ + Fri, 24 Mar 2023 20:48:24 +0800 + https://liudon.com/tags/%E4%B8%AA%E4%BA%BA%E6%8A%80%E8%83%BD/ + + + + 自己动手 + https://liudon.com/tags/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B/ + Mon, 16 Jan 2023 14:43:38 +0800 + https://liudon.com/tags/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B/ + + + + CLS + https://liudon.com/tags/cls/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/tags/cls/ + + + + PagerMod + https://liudon.com/tags/pagermod/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/tags/pagermod/ + + + + Cloudflare Pages + https://liudon.com/tags/cloudflare-pages/ + Fri, 29 Jul 2022 23:16:01 +0800 + https://liudon.com/tags/cloudflare-pages/ + + + + 带着相机晒太阳 + https://liudon.com/tags/%E5%B8%A6%E7%9D%80%E7%9B%B8%E6%9C%BA%E6%99%92%E5%A4%AA%E9%98%B3/ + Thu, 21 Jul 2022 20:40:20 +0800 + https://liudon.com/tags/%E5%B8%A6%E7%9D%80%E7%9B%B8%E6%9C%BA%E6%99%92%E5%A4%AA%E9%98%B3/ + + + + 北京幼升小 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC%E5%B9%BC%E5%8D%87%E5%B0%8F/ + + + + 幼升小 + https://liudon.com/tags/%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/tags/%E5%B9%BC%E5%8D%87%E5%B0%8F/ + + + + 非京籍 + https://liudon.com/tags/%E9%9D%9E%E4%BA%AC%E7%B1%8D/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/tags/%E9%9D%9E%E4%BA%AC%E7%B1%8D/ + + + + 北京 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC/ + + + + 疫情 + https://liudon.com/tags/%E7%96%AB%E6%83%85/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/tags/%E7%96%AB%E6%83%85/ + + + + 博客 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2/ + + + + 收录 + https://liudon.com/tags/%E6%94%B6%E5%BD%95/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/tags/%E6%94%B6%E5%BD%95/ + + + + SSD + https://liudon.com/tags/ssd/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/tags/ssd/ + + + + Thinkpad + https://liudon.com/tags/thinkpad/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/tags/thinkpad/ + + + + 三星 + https://liudon.com/tags/%E4%B8%89%E6%98%9F/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/tags/%E4%B8%89%E6%98%9F/ + + + + 硬盘 + https://liudon.com/tags/%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/tags/%E7%A1%AC%E7%9B%98/ + + + + Mysql + https://liudon.com/tags/mysql/ + Mon, 14 Dec 2020 18:47:29 +0800 + https://liudon.com/tags/mysql/ + + + + 公积金 + https://liudon.com/tags/%E5%85%AC%E7%A7%AF%E9%87%91/ + Fri, 17 Jan 2020 17:14:32 +0800 + https://liudon.com/tags/%E5%85%AC%E7%A7%AF%E9%87%91/ + + + + Php + https://liudon.com/tags/php/ + Tue, 26 Nov 2019 19:56:18 +0800 + https://liudon.com/tags/php/ + + + + Nginx + https://liudon.com/tags/nginx/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/tags/nginx/ + + + + Ssl + https://liudon.com/tags/ssl/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/tags/ssl/ + + + + Swoft + https://liudon.com/tags/swoft/ + Thu, 26 Sep 2019 13:14:23 +0800 + https://liudon.com/tags/swoft/ + + + + Git + https://liudon.com/tags/git/ + Fri, 06 Sep 2019 15:13:51 +0800 + https://liudon.com/tags/git/ + + + + Curl + https://liudon.com/tags/curl/ + Wed, 04 Sep 2019 11:07:46 +0800 + https://liudon.com/tags/curl/ + + + + Flink + https://liudon.com/tags/flink/ + Thu, 28 Mar 2019 13:00:50 +0800 + https://liudon.com/tags/flink/ + + + + diff --git a/tags/ipfs/index.html b/tags/ipfs/index.html new file mode 100644 index 000000000..40b61cecb --- /dev/null +++ b/tags/ipfs/index.html @@ -0,0 +1,11 @@ +Ipfs | 流动 +

      让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务

      本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。 +背景 周末更新博客时,发现workflow的上传IPFS任务执行失败了。 +...

      2024-09-04 · 3 min · 1246 words · Liudon

      搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway

      背景 4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。 +没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。 +...

      2024-05-22 · 3 min · 1262 words · Liudon
      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      将博客部署到星际文件系统(IPFS)

      在这篇文章,我将会介绍如何利用Github Actions将hugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。 +IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。 +...

      2023-02-21 · 3 min · 1326 words · Liudon
      \ No newline at end of file diff --git a/tags/ipfs/index.xml b/tags/ipfs/index.xml new file mode 100644 index 000000000..66b8ed194 --- /dev/null +++ b/tags/ipfs/index.xml @@ -0,0 +1,470 @@ + + + + Ipfs on 流动 + https://liudon.com/tags/ipfs/ + Recent content in Ipfs on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 04 Sep 2024 22:39:37 +0800 + + + 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + Wed, 04 Sep 2024 22:39:37 +0800 + https://liudon.com/posts/keep-your-ipfs-site-online-with-filebase-ipns/ + <p>本文会介绍如何接入<code>filebase</code>的Names(IPNS)服务,使你的<code>IPFS</code>站点持久在线。</p> +<h4 id="背景">背景</h4> +<p>周末更新博客时,发现workflow的上传IPFS任务执行失败了。</p> + 本文会介绍如何接入filebase的Names(IPNS)服务,使你的IPFS站点持久在线。

      +

      背景

      +

      周末更新博客时,发现workflow的上传IPFS任务执行失败了。

      +
      Run aquiladev/ipfs-action@master
      +Error: RequestInit: duplex option is required when sending a body.
      +node:internal/deps/undici/undici:12502
      +      Error.captureStackTrace(err, this);
      +            ^
      +
      +TypeError: RequestInit: duplex option is required when sending a body.
      +    at node:internal/deps/undici/undici:12502:13
      +    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      +
      +Node.js v20.13.1
      +

      查了一下,应该是Github更新了NodeJS版本导致的。

      +
      The following actions use a deprecated Node.js version and will be forced to run on node20: actions/checkout@v2, peaceiris/actions-hugo@v2, peaceiris/actions-gh-pages@v3, aquiladev/ipfs-action@master. For more info: https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
      +

      研究了一下,问题在于js-ipfs包的fetch方法没有传duplex参数导致。

      +

      Github文档,官方已经不再更新了。

      +
      DEPRECATED: js-IPFS has been superseded by Helia
      +

      搜索一番,发现了两个包heliajs-kubo-rpc-client

      +

      helia调用方法有变化js-kubo-rpc-client和原来的js-ipfs使用一致。

      +

      捣鼓了一番,没调通,不懂前端的锅,只能放弃,顺便给作者提了个issue,还是作者来适配吧。

      +

      隔天看的时候,在Pull requests里发现已经有升级后的提交了。

      +

      哈哈,原来filebase官方早就升级适配了,filebase/ipfs-action,顺带发现居然还支持了IPNS更新,太完美了!!!

      +

      折腾记录

      +

      关于IPNS的作用,可以参考zu1k大佬的IPFS 新手指北

      +

      关于IPFS的部署,可以参考我的将博客部署到星际文件系统(IPFS)

      +
      生成密钥
      +

      因为我在云主机上部署了ipfs服务,已经有在更新IPNS

      +

      这里引入filebase后,相当于多个节点来更新,需要保证IPNS地址上一致的。

      +

      所以需要将云主机的密钥导出后,导入到filebase

      +

      之前使用的是ipfs默认密钥,这个是无法导出的,所以只能重新生成一个密钥, +ipfs-action为密钥名字,改成你自己的:

      +
      ipfs key gen ipfs-action
      +
      +> k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl
      +

      注意:filebase还不支持type/size参数,这里必须使用默认方式创建,否则在filebase导入已有密钥会报错。

      +

      + +chat + + +

      +

      查看已有密钥:

      +
      ipfs key list -l
      +> k51qzi5uqu5djx9olgjcibdiurrr09w75v6rdfx0cvwye295k787sssssf0d9d self        
      +> k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ossssnc7uwg ipfs-action
      +

      导出密钥:

      +
      ipfs key export ipfs-action
      +

      执行后,当前目录下会生成一个ipfs-action.key文件,内容为二进制。

      +

      filebase导入key要求为base64编码,将其转为base64编码:

      +
      cat ipfs-action.key | base64
      +> 5oiR5piv5rWL6K+V
      +

      记住这里的base64内容,下面会用到。

      +
      创建NAME
      +

      进入filebase控制台,点击Create Name

      +

      + +input + + +

      +
      Label: 备注,可以随便填
      +CID: 填入IPFS的cid地址
      +Name Network: 固定选IPNS
      +Enabled:固定选Yes
      +Import Existing Private Key (Optional):填入第一步的base64内容
      +

      确定提交。

      +
      修改workflow
      +
      - name: IPFS upload to filebase
      +uses: filebase/ipfs-action@master
      +with:
      +    path: ./public
      +    service: filebase
      +    pinName: ipfs-action
      +    filebaseBucket: ${{ secrets.FILEBASE_BUCKET }}
      +    filebaseKey: ${{ secrets.FILEBASE_KEY }}
      +    filebaseSecret: ${{ secrets.FILEBASE_SECRET }}
      +    key: ipfs-action
      +

      新增key参数,值为第二步Label填入的内容。

      +

      提交后,执行workflow,在执行结果里找到IPNS地址。

      +
      Run filebase/ipfs-action@master
      +Parsing options...
      +Parsed Options: {"path":"/home/runner/work/***.github.io/***.github.io/public","service":"filebase","host":"ipfs.io","port":"5001","protocol":"https","headers":{},"key":"ipfs-action","pinName":"ipfs-action","pinataKey":"","pinataSecret":"","pinataPinName":"","filebaseBucket":"***","filebaseKey":"***","filebaseSecret":"***","infuraProjectId":"","infuraProjectSecret":"","timeout":"60000","verbose":false,"pattern":"public/**/*"}
      +Adding files...
      +Starting filebase client
      +Started filebase client
      +Storing files...
      +Stored files...
      +CID: bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea
      +Updating name...
      +Updated name...
      +Done
      +Upload to IPFS finished successfully {
      +  cid: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipfs: 'bafybeihagzsdupyrecky7bnstzckgf5flxbrdz542jmfaep4xtbj6aa2ea',
      +  ipns: 'k51qzi5uqu5dktnw0vc8j9ci42e8gk741ici7ofpv40mo4f6e1ovj1isnc7uwg'
      +}
      +
      +

      更新域名的dnslink值:

      +

      普通域名

      +

      + +dns + + +

      +

      eth域名

      +

      + +eth + + +

      +

      第一次和外国人在线沟通,时差原因搞了两天才把filebase导入报错的问题解决。 😂😂😂

      +

      另外吐槽一下filebase服务,sdk已经有相关实现了,文档都还没更新。

      +]]>
      +
      + + 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + Wed, 22 May 2024 21:20:20 +0800 + https://liudon.com/posts/replacing-cloudflare-ipfs-gateway-with-self-hosted-gateway/ + <h4 id="背景">背景</h4> +<p>4月底的时候,Livid大佬提醒,<code>Cloudflare</code>应该是调整了<code>IPFS Gateway</code>网关策略,我的<a href="https://liudon.xyz">IPFS镜像博客</a>无法访问了。</p> +<p>没查到<code>Cloudflare</code>的调整说明,不过还好<code>IPFS</code>官方也提供了公共网关<code>gateway.ipfs.io</code>,将域名解析改到官网网关。</p> + 背景 +

      4月底的时候,Livid大佬提醒,Cloudflare应该是调整了IPFS Gateway网关策略,我的IPFS镜像博客无法访问了。

      +

      没查到Cloudflare的调整说明,不过还好IPFS官方也提供了公共网关gateway.ipfs.io,将域名解析改到官网网关。

      +

      但还是无法访问,被Cloudflare拦截了。

      +
      Error 1014 Ray ID: 887cc7fcfa2804bb • 2024-05-22 12:24:05 UTC
      +CNAME Cross-User Banned
      +What happened?
      +You've requested a page on a website that is part of the Cloudflare network. The host is configured as a CNAME across accounts on Cloudflare, which is not allowed by Cloudflare's security policy.
      +
      +What can I do?
      +If this is an R2 custom domain, it may still be initializing. If you have attempted to manually point a CNAME DNS record to your R2 bucket, you must do it using a custom domain. Refer to R2's documentation for details.
      +
      +
      +Visit our website to learn more about Cloudflare.
      +

      这周在Discord群里,看到有人发消息,说是Cloudflare将下线IPFS Gateway网关服务。

      +

      https://blog.cloudflare.com/cloudflares-public-ipfs-gateways-and-supporting-interplanetary-shipyard

      +
      +

      All traffic using the cloudflare-ipfs.com or cf-ipfs.com hostname(s) will continue to work without interruption and be redirected to ipfs.io or dweb.link until August 14th, 2024, at which time the Cloudflare hostnames will no longer connect to IPFS and all users must switch the hostname they use to ipfs.io or dweb.link to ensure no service interruption takes place. If you are using either of the Cloudflare hostnames, please be sure to switch to one of the new ones as soon as possible ahead of the transition date to avoid any service interruptions!

      +
      +

      方案调研

      +

      经过一番搜索,找到了一篇自建IPFS Gateway网关的资料,里面用到了bifrost-gateway组件。

      +
      To run against a compatible, local trustless gateway provided by Kubo or IPFS Desktop:
      +
      +$ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
      +

      看文档,可以通过这个命令搭建一个自己的网关服务,同时支持DNSLink方式访问。

      +

      太棒了,感觉可以自己搭一套网关,然后用Nginx反代对外提供服务。

      +

      在之前将博客部署到星际文件系统(IPFS)文章中,已经通过Kubo搭建了一套本地IPFS服务。

      +

      上机器验证一下可行性:

      +
        +
      1. +

        启动Bifrost Gateway,网关默认地址为https://127.0.0.1:8081

        +
        $ PROXY_GATEWAY_URL="http://127.0.0.1:8080" ./bifrost-gateway
        +2024/05/22 20:54:00 Starting bifrost-gateway dev-build
        +2024/05/22 20:54:00 Proxy backend (PROXY_GATEWAY_URL) at http://127.0.0.1:8080
        +2024/05/22 20:54:00 BLOCK_CACHE_SIZE: 1024
        +2024/05/22 20:54:00 GRAPH_BACKEND: false
        +2024/05/22 20:54:00 Legacy RPC at /api/v0 (KUBO_RPC_URL) provided by http://127.0.0.1:5001
        +2024/05/22 20:54:00 Path gateway listening on http://127.0.0.1:8081
        +2024/05/22 20:54:00   Smoke test (JPG): http://127.0.0.1:8081/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
        +2024/05/22 20:54:00 Subdomain gateway configured on dweb.link and http://localhost:8081
        +2024/05/22 20:54:00   Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:8081/ipns/en.wikipedia-on-ipfs.org/wiki/
        +2024/05/22 20:54:00 Metrics exposed at http://127.0.0.1:8041/debug/metrics/prometheus
        +
      2. +
      3. +

        在另外一个终端下,执行命令

        +
        $ curl 'http://127.0.0.1:8081/' -H"Host:liudon.xyz" -I
        +HTTP/1.1 200 OK
        +Accept-Ranges: bytes
        +Access-Control-Allow-Headers: Content-Type
        +Access-Control-Allow-Headers: Range
        +Access-Control-Allow-Headers: User-Agent
        +Access-Control-Allow-Headers: X-Requested-With
        +Access-Control-Allow-Methods: GET
        +Access-Control-Allow-Methods: HEAD
        +Access-Control-Allow-Methods: OPTIONS
        +Access-Control-Allow-Origin: *
        +Access-Control-Expose-Headers: Content-Length
        +Access-Control-Expose-Headers: Content-Range
        +Access-Control-Expose-Headers: X-Chunked-Output
        +Access-Control-Expose-Headers: X-Ipfs-Path
        +Access-Control-Expose-Headers: X-Ipfs-Roots
        +Access-Control-Expose-Headers: X-Stream-Output
        +Content-Length: 26283
        +Content-Type: text/html
        +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
        +Last-Modified: Wed, 22 May 2024 12:57:29 GMT
        +X-Ipfs-Path: /ipns/liudon.xyz/
        +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
        +Date: Wed, 22 May 2024 12:57:29 GMT
        +
      4. +
      +

      验证可行,不过我记得Kubo默认就有网关服务的,试一下直接通过Kubo默认网关的情况。

      +

      Kubo默认网关地址为http://127.0.0.1:8080,注意不要对外网提供8080端口访问,否则会被别人当成公共网关使用

      +
      $ curl 'http://127.0.0.1:8080/' -H"Host:liudon.xyz" -I
      +HTTP/1.1 200 OK
      +Accept-Ranges: bytes
      +Access-Control-Allow-Headers: Content-Type
      +Access-Control-Allow-Headers: Range
      +Access-Control-Allow-Headers: User-Agent
      +Access-Control-Allow-Headers: X-Requested-With
      +Access-Control-Allow-Methods: GET
      +Access-Control-Allow-Origin: *
      +Access-Control-Expose-Headers: Content-Length
      +Access-Control-Expose-Headers: Content-Range
      +Access-Control-Expose-Headers: X-Chunked-Output
      +Access-Control-Expose-Headers: X-Ipfs-Path
      +Access-Control-Expose-Headers: X-Ipfs-Roots
      +Access-Control-Expose-Headers: X-Stream-Output
      +Content-Length: 26283
      +Content-Type: text/html
      +Etag: "QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj"
      +Last-Modified: Wed, 22 May 2024 12:59:25 GMT
      +X-Ipfs-Path: /ipns/liudon.xyz/
      +X-Ipfs-Roots: QmebCXeD6XDB9xsVvX5Te91EeF5t7sk65A3adsLQ9bostj
      +Date: Wed, 22 May 2024 12:59:25 GMT
      +

      也是可以的,那就没必要多搞一套bifrost网关了。

      +

      具体实现

      +

      通过Nginx反向代理转发到本地IPFS网关,只需要改一下解析就可以继续使用IPFS服务了。

      +

      + +方案 + + +

      +
        +
      1. Nginx反向代理
      2. +
      +
      server {
      +    listen 443 ssl http2;
      +    server_name liudon.xyz;
      +
      +    ssl_certificate /etc/nginx/ssl/liudon.xyz/fullchain.cer;
      +    ssl_certificate_key /etc/nginx/ssl/liudon.xyz/liudon.xyz.key;
      +
      +    ssl_protocols TLSv1.2 TLSv1.3;
      +    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
      +    ssl_prefer_server_ciphers on;
      +    ssl_session_cache shared:SSL:10m;
      +    ssl_session_timeout 10m;
      +
      +    location / {
      +            proxy_pass http://127.0.0.1:8080;
      +            proxy_set_header Host $host; // 注意这里要传递反代的域名信息,限制只能访问我们自己dnslink对应的资源
      +    }
      +
      +    access_log /var/log/nginx/liudon.xyz.access.log;
      +    error_log /var/log/nginx/liudon.xyz.error.log;
      +}
      +

      申请Let's Encrypt证书,证书相关的就不多做介绍了,网上资料很多。

      +
        +
      1. 更改DNS解析
      2. +
      +
      原有的解析
      +
      +类型:CNAME
      +名称:liudon.xyz
      +内容:cloudflare-ipfs.com
      +
      +新的解析
      +
      +类型:A
      +名称:liudon.xyz
      +内容:你的服务器公网IP
      +

      搞定,又可以继续白嫖IPFS服务了。

      +]]>
      +
      + + 将博客部署到星际文件系统(IPFS) + https://liudon.com/posts/deploy-blog-to-ipfs/ + Tue, 21 Feb 2023 19:46:58 +0800 + https://liudon.com/posts/deploy-blog-to-ipfs/ + <p>在这篇文章,我将会介绍如何利用<code>Github Actions</code>将<code>hugo</code>博客自动部署到<code>IPFS</code>上,并通过自定义域名访问<code>IPFS</code>上的文件。</p> +<blockquote> +<p>IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。</p> + 在这篇文章,我将会介绍如何利用Github Actionshugo博客自动部署到IPFS上,并通过自定义域名访问IPFS上的文件。

      +
      +

      IPFS(InterPlanetary File System)中文称为星际文件系统,是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。

      +
      +

      照惯例,先上演示.访问我的IPFS博客

      +

      欢迎各位pin我的博客, ipfs pin add /ipns/liudon.xyz

      +
      curl 'https://liudon.xyz' -I
      +HTTP/2 200 
      +date: Tue, 21 Feb 2023 23:59:18 GMT
      +content-type: text/html
      +vary: Accept-Encoding
      +access-control-allow-methods: GET
      +access-control-allow-methods: GET, POST, OPTIONS
      +last-modified: Tue, 21 Feb 2023 23:59:18 GMT
      +x-ipfs-gateway-host: ipfs-bank1-sv15
      +x-ipfs-path: /ipns/liudon.xyz/
      +x-ipfs-roots: Qmd4pnpUj8CaLKoVMJNHJyrqwWVa4wvz1qKxZsU9vKgErL
      +x-ipfs-pop: ipfs-bank1-sv15
      +timing-allow-origin: *
      +access-control-allow-origin: *
      +access-control-allow-headers: X-Requested-With, Range, Content-Range, X-Chunked-Output, X-Stream-Output
      +access-control-expose-headers: Content-Range, X-Chunked-Output, X-Stream-Output
      +x-ipfs-lb-pop: gateway-bank1-sv15
      +x-proxy-cache: MISS
      +cf-cache-status: DYNAMIC
      +report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FLcvWgtngoLuGZkl9jYsviSoOlSoE2Y0rKxI3bgNaKRxhNOrIm6nozqVzndav3%2B9QrvvcJ5GNmC11JBlN8tiigbF9CWPW33TbnLKyfdeblOcEhmZINTcC%2BJ6xhKs"}],"group":"cf-nel","max_age":604800}
      +nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
      +server: cloudflare
      +cf-ray: 79d36f598970531f-LAX
      +alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
      +

      准备工作:

      +
        +
      1. Cloudflare帐号
      2. +
      3. 一台VPS主机,我用到腾讯云lighthouse主机2核2G
      4. +
      5. 一个域名
      6. +
      +

      方案介绍:

      +

      + +实现方案 + + +

      +
        +
      1. 在VPS主机上安装启动IPFS服务,通过端口5001在内网提供API服务.
      2. +
      3. 在GitHub上通过ssh建立端口转发,本地端口5001转发到VPS主机5001.
      4. +
      5. 在GitHub上利用ipfs-http-client上传文进到5001端口.
      6. +
      7. 绑定域名到IPNS地址,通过域名访问IPFS文件.
      8. +
      +

      1. 部署IPFS服务

      +
        +
      • +

        安装kubo,详见官方文档

        +
        wget https://dist.ipfs.tech/kubo/v0.18.1/kubo_v0.18.1_linux-amd64.tar.gz
        +
        +tar -xvzf kubo_v0.18.1_linux-amd64.tar.gz
        +
        +> x kubo/install.sh
        +> x kubo/ipfs
        +> x kubo/LICENSE
        +> x kubo/LICENSE-APACHE
        +> x kubo/LICENSE-MIT
        +> x kubo/README.md
        +
        +cd kubo
        +sudo bash install.sh
        +
        +> Moved ./ipfs to /usr/local/bin
        +
        +ipfs --version
        +
        +> ipfs version 0.18.1
        +
      • +
      • +

        初始化IPFS

        +
        ipfs init --profile=server
        +
      • +
      • +

        添加到开机启动

        +
        [Unit]
        +
        +Description=IPFS Daemon
        +After=syslog.target network.target remote-fs.target nss-lookup.target
        +
        +[Service]
        +Type=simple
        +ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
        +User=root
        +
        +[Install]
        +WantedBy=multi-user.target
        +

        注意打开--enable-namesys-pubsub参数,不然IPNS更新生效很慢。

        +

        将上述代码保存到/usr/lib/systemd/system/ipfs.service文件.

        +

        启动进程.

        +
        systemctl start ipfs.service
        +
      • +
      • +

        开放端口

        +

        IPFS默认通过4001端口跟DHT网络通信,需要放开4001端口访问.

        +
      • +
      +

      2. GitHub Actions配置

      +

      博客我使用的Hugo,原有的工作流方案见将博客部署到Cloudflare Pages

      +

      完整的工作流配置见main.yml

      +
        +
      • +

        添加如下变量到Actions secrets

        +
        SSHKEY VPS主机ssh登陆私钥
        +SSHHOST ssh用户@VPS机器IP,类似root@127.0.0.1
        +
      • +
      • +

        更新yaml配置文件,添加如下任务.

        +
           - name: Connect to ssh in BG
        +    timeout-minutes: 2
        +    run: | 
        +      echo "${{ secrets.SSHKEY }}" > ../privkey
        +      chmod 600 ../privkey
        +      ssh -o StrictHostKeyChecking=no ${{ secrets.SSHHOST }} -i ../privkey -L 5001:localhost:5001 -fTN
        +
        +  - name: ipfs upload
        +    uses: aquiladev/ipfs-action@master
        +    id: deploy
        +    timeout-minutes: 2
        +    with:
        +      path: ./public
        +      service: ipfs
        +      verbose: true
        +      host: localhost
        +      port: 5001
        +      protocol: http
        +      key: self # 要配置key,这样才会生成IPNS地址
        +

        测试执行action,日志里会有类似如下输出.

        +
        Upload to IPFS finished successfully {
        +cid: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipfs: 'QmST2Zqv8qffFTVuqfRX57uzqxsoQtTYinmHpyLh7padAD',
        +ipns: '12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c'
        +}
        +

        每次执行,ipfs地址不同,ipns地址不变. +记住这里到ipns地址,下面会用到.

        +
      • +
      +

      3. 域名配置

      +

      Cloudflare上添加解析:

      +
        +
      • 添加DNS TXT记录,名称为_dnslink,值为dnslink=/ipns/12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c,将这里的12D3KooWKvJ9Y4D5X4R3ajuc7tVtQWXZMG4iiMCFtay8frM66o4c改为上一步日志里到ipns值。
      • +
      • 添加DNS CNNANE记录,名称为你的域名,值为gateway.ipfs.io.
      • +
      +

      + +DNS解析 + + +

      +

      4. 开启相对路径

      +

      Livid大佬提醒,公共网关访问时存在相对路径问题

      +

      我用的Hugo,配置文件里打开relativeURLs配置。

      +
      relativeURLs: true
      +

      从年前开始想怎么做成自动化,到今天终于跑通搞定了.😁😁😁

      +

      + +VPS主机运行情况 + + +

      +

      两天跑了14G流量,每月的流量资源包基本够用了.

      +

      参考资料:

      +

      IPFS 日用优化指南

      +

      参考配置

      +]]>
      +
      +
      +
      diff --git a/tags/ipfs/page/1/index.html b/tags/ipfs/page/1/index.html new file mode 100644 index 000000000..37e43e99a --- /dev/null +++ b/tags/ipfs/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/ipfs/ + \ No newline at end of file diff --git a/tags/mysql/index.html b/tags/mysql/index.html new file mode 100644 index 000000000..515715af7 --- /dev/null +++ b/tags/mysql/index.html @@ -0,0 +1,10 @@ +Mysql | 流动 +

      mysql中字符串和整型自动转换的问题

      表结构如下 +desc info; +-------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------+------+-----+---------+----------------+ | id | int(8) unsigned | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | +-------+-----------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) 执行sql. +insert into info values ('', 'xxx'); insert into info values ('', 'yyy'); 查询记录. +select * from info; +----+------+ | id | name | +----+------+ | 1 | xxx | | 2 | yyy | +----+------+ 2 rows in set (0.00 sec) 执行下面sql. +...

      2020-12-14 · 1 min · 303 words · Liudon

      一次惊心动魄的Mysql更新操作

      问题描述 # 表结构 MySQL > desc user_packages; +----------------+---------------------+------+-----+---------------------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+---------------------+------+-----+---------------------+----------------+ | up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | start_date | date | NO | | NULL | | | end_date | date | NO | | NULL | | | up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | | up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +----------------+---------------------+------+-----+---------------------+----------------+ 5 rows in set (0.00 sec) MySQL > select * from user_packages limit 5; +-------+------------+------------+ | up_id | start_date | end_date | +-------+------------+------------+ | 185 | 2018-04-01 | 2018-06-30 | | 186 | 2018-04-01 | 2018-06-30 | | 187 | 2018-04-01 | 2018-06-30 | | 188 | 2018-04-01 | 2018-06-30 | | 189 | 2018-04-01 | 2018-06-30 | +-------+------------+------------+ 5 rows in set (0.00 sec) 操作过程 需要更新某条记录的end_date字段,执行sql如下: +...

      2020-05-19 · 2 min · 776 words · Liudon
      \ No newline at end of file diff --git a/tags/mysql/index.xml b/tags/mysql/index.xml new file mode 100644 index 000000000..4c42781be --- /dev/null +++ b/tags/mysql/index.xml @@ -0,0 +1,197 @@ + + + + Mysql on 流动 + https://liudon.com/tags/mysql/ + Recent content in Mysql on 流动 + Hugo -- 0.134.3 + zh-cn + Mon, 14 Dec 2020 18:47:29 +0800 + + + mysql中字符串和整型自动转换的问题 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + Mon, 14 Dec 2020 18:47:29 +0800 + https://liudon.com/posts/strings-and-numbers-in-mysql/ + <p>表结构如下</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span>desc info; +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> Field <span style="color:#f92672">|</span> Type <span style="color:#f92672">|</span> Null <span style="color:#f92672">|</span> Key <span style="color:#f92672">|</span> Default <span style="color:#f92672">|</span> Extra <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> id <span style="color:#f92672">|</span> <span style="color:#a6e22e">int</span>(<span style="color:#ae81ff">8</span>) unsigned <span style="color:#f92672">|</span> NO <span style="color:#f92672">|</span> PRI <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> auto_increment <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> name <span style="color:#f92672">|</span> varchar(<span style="color:#ae81ff">20</span>) <span style="color:#f92672">|</span> YES <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> NULL <span style="color:#f92672">|</span> <span style="color:#f92672">|</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">+-------+-----------------+------+-----+---------+----------------+</span> +</span></span><span style="display:flex;"><span><span style="color:#ae81ff">2</span> rows <span style="color:#f92672">in</span> set (<span style="color:#ae81ff">0.00</span> sec) +</span></span></code></pre></div><p>执行sql.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;xxx&#39;); +</span></span><span style="display:flex;"><span>insert into info values (&#39;&#39;, &#39;yyy&#39;); +</span></span></code></pre></div><p>查询记录.</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>select * from info; +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| id | name | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>| 1 | xxx | +</span></span><span style="display:flex;"><span>| 2 | yyy | +</span></span><span style="display:flex;"><span>+----+------+ +</span></span><span style="display:flex;"><span>2 rows in set (0.00 sec) +</span></span></code></pre></div><p>执行下面sql.</p> + 表结构如下

      +
      desc info;
      ++-------+-----------------+------+-----+---------+----------------+
      +| Field | Type            | Null | Key | Default | Extra          |
      ++-------+-----------------+------+-----+---------+----------------+
      +| id    | int(8) unsigned | NO   | PRI | NULL    | auto_increment |
      +| name  | varchar(20)     | YES  |     | NULL    |                |
      ++-------+-----------------+------+-----+---------+----------------+
      +2 rows in set (0.00 sec)
      +

      执行sql.

      +
      insert into info values ('', 'xxx');
      +insert into info values ('', 'yyy');
      +

      查询记录.

      +
      select * from info;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      +|  2 | yyy  |
      ++----+------+
      +2 rows in set (0.00 sec)
      +

      执行下面sql.

      +
      select * from info where id = 1;
      +
      +select * from info where id = '1aaaa';
      +

      你先想想结果会是什么。

      +
      select * from info where id = 1;
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set (0.00 sec)
      +
      +select * from info where id = '1aaaa';
      ++----+------+
      +| id | name |
      ++----+------+
      +|  1 | xxx  |
      ++----+------+
      +1 row in set, 1 warning (0.01 sec)
      +

      两个sql都查到了id = 1的记录,唯一的区别在于第二个sql有一个warning错误。

      +
      show warnings;
      ++---------+------+-------------------------------------------+
      +| Level   | Code | Message                                   |
      ++---------+------+-------------------------------------------+
      +| Warning | 1292 | Truncated incorrect DOUBLE value: '1aaaa' |
      ++---------+------+-------------------------------------------+
      +1 row in set (0.00 sec)
      +

      mysql在查询时,会根据字段类型进行转换,这里1aaaa被转为了1

      +]]>
      +
      + + 一次惊心动魄的Mysql更新操作 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + Tue, 19 May 2020 17:16:53 +0800 + https://liudon.com/posts/%E4%B8%80%E6%AC%A1%E6%83%8A%E5%BF%83%E5%8A%A8%E9%AD%84%E7%9A%84mysql%E6%9B%B4%E6%96%B0%E6%93%8D%E4%BD%9C/ + <h4 id="问题描述">问题描述</h4> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span># 表结构 +</span></span><span style="display:flex;"><span>MySQL &gt; desc user_packages; +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| Field | Type | Null | Key | Default | Extra | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>| up_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | +</span></span><span style="display:flex;"><span>| start_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| end_date | date | NO | | NULL | | +</span></span><span style="display:flex;"><span>| up_created | datetime | NO | MUL | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>| up_updated | datetime | NO | | 0000-00-00 00:00:00 | | +</span></span><span style="display:flex;"><span>+----------------+---------------------+------+-----+---------------------+----------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>MySQL &gt; select * from user_packages limit 5; +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| up_id | start_date | end_date | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>| 185 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 186 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 187 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 188 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>| 189 | 2018-04-01 | 2018-06-30 | +</span></span><span style="display:flex;"><span>+-------+------------+------------+ +</span></span><span style="display:flex;"><span>5 rows in set (0.00 sec) +</span></span></code></pre></div><h4 id="操作过程">操作过程</h4> +<p>需要更新某条记录的<code>end_date</code>字段,执行sql如下:</p> + 问题描述 +
      # 表结构
      +MySQL > desc user_packages;
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| Field          | Type                | Null | Key | Default             | Extra          |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +| up_id          | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
      +| start_date     | date                | NO   |     | NULL                |                |
      +| end_date       | date                | NO   |     | NULL                |                |
      +| up_created     | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
      +| up_updated     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
      ++----------------+---------------------+------+-----+---------------------+----------------+
      +5 rows in set (0.00 sec)
      +
      +MySQL > select * from user_packages limit 5;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 2018-06-30 |
      +|   186 | 2018-04-01 | 2018-06-30 |
      +|   187 | 2018-04-01 | 2018-06-30 |
      +|   188 | 2018-04-01 | 2018-06-30 |
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +5 rows in set (0.00 sec)
      +

      操作过程

      +

      需要更新某条记录的end_date字段,执行sql如下:

      +
      MySQL > update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +Query OK, 1 row affected, 1 warning (0.00 sec)
      +Rows matched: 1  Changed: 1  Warnings: 1
      +

      执行完,发现sql写错了!!!!

      +

      正确的sql应该是:

      +
      update user_packages set end_date = '2020-06-06' where up_id = 189 limit 1;
      +

      误把where写成了and,还好指定了limit = 1,只操作了一条记录。

      +

      回滚

      +

      回滚的前提,要先找到更新的那条记录。

      +

      up_id为表的主键,更新前表里已经有这条记录了,主键不能重复,感觉语句应该没有执行成功。

      +
      MySQL > select * from user_packages where up_id = 189;
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   189 | 2018-04-01 | 2018-06-30 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      执行查询语句,表里确实也只有这一条up_id=189的记录。

      +

      感觉这个update语句应该没执行成功,但是没执行成功应该报错的呀。

      +

      这个时候把希望放到了语句结果里的Warnings: 1,是不是没执行成功呢。

      +

      因为紧接着又执行了其他语句,所以也无法通过show warnings查看具体的错误信息了。

      +

      那么这条语句到底执行成功了吗?如果执行成功,那么修改的是哪条记录呢?

      +

      这里通过一番查找,终于定位到了记录。

      +

      AND分隔符,在mysql语句里优先级最高。

      +
      update user_packages set end_date = '2020-06-06' and up_id = 189 limit 1;
      +
      +等效为
      +
      +update user_packages set end_date = ('2020-06-06' and up_id = 189) limit 1;
      +
      +即
      +
      +update user_packages set end_date = 0 limit 1;
      +

      因为end_date字段为date类型,所以写入表后的记录为0000-00-00

      +
      MySQL > select * from user_packages where end_date = '0000-00-00';
      ++-------+------------+------------+
      +| up_id | start_date | end_date   |
      ++-------+------------+------------+
      +|   185 | 2018-04-01 | 0000-00-00 |
      ++-------+------------+------------+
      +1 rows in set (0.00 sec)
      +

      好在这次只更新了一条记录,否则后果无法想象。

      +

      切记不要在现网直接操作DB。

      +

      相关资料:

      +

      一个我认为是bug的UPDATE语句

      +]]>
      +
      +
      +
      diff --git a/tags/mysql/page/1/index.html b/tags/mysql/page/1/index.html new file mode 100644 index 000000000..fc9bd6660 --- /dev/null +++ b/tags/mysql/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/mysql/ + \ No newline at end of file diff --git a/tags/netlify/index.html b/tags/netlify/index.html new file mode 100644 index 000000000..6b766e7c5 --- /dev/null +++ b/tags/netlify/index.html @@ -0,0 +1,7 @@ +Netlify | 流动 +

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon
      \ No newline at end of file diff --git a/tags/netlify/index.xml b/tags/netlify/index.xml new file mode 100644 index 000000000..7421cacb1 --- /dev/null +++ b/tags/netlify/index.xml @@ -0,0 +1,216 @@ + + + + Netlify on 流动 + https://liudon.com/tags/netlify/ + Recent content in Netlify on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 19 Oct 2023 19:46:32 +0800 + + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      +
      +
      diff --git a/tags/netlify/page/1/index.html b/tags/netlify/page/1/index.html new file mode 100644 index 000000000..f1bc12b8f --- /dev/null +++ b/tags/netlify/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/netlify/ + \ No newline at end of file diff --git a/tags/nginx/index.html b/tags/nginx/index.html new file mode 100644 index 000000000..6f13106bc --- /dev/null +++ b/tags/nginx/index.html @@ -0,0 +1,7 @@ +Nginx | 流动 +

      检测网站支持的SSL/TLS协议版本

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。 +为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。 +...

      2019-11-14 · 1 min · 205 words · Liudon
      \ No newline at end of file diff --git a/tags/nginx/index.xml b/tags/nginx/index.xml new file mode 100644 index 000000000..acab23e79 --- /dev/null +++ b/tags/nginx/index.xml @@ -0,0 +1,42 @@ + + + + Nginx on 流动 + https://liudon.com/tags/nginx/ + Recent content in Nginx on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 14 Nov 2019 14:48:08 +0800 + + + 检测网站支持的SSL/TLS协议版本 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + <blockquote> +<p>Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。</p> +</blockquote> +<p>为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。</p> + +

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。

      + +

      为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。

      +
        +
      1. +

        检测是否支持TLSv1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1
        +
      2. +
      3. +

        检测是否支持TLSv1.1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_1
        +
      4. +
      5. +

        检测是否支持TLSv1.2

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_2
        +
      6. +
      +

      参考资料:How to check what SSL/TLS versions are available for a website?

      +]]>
      +
      +
      +
      diff --git a/tags/nginx/page/1/index.html b/tags/nginx/page/1/index.html new file mode 100644 index 000000000..f562bf3a5 --- /dev/null +++ b/tags/nginx/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/nginx/ + \ No newline at end of file diff --git a/tags/pagermod/index.html b/tags/pagermod/index.html new file mode 100644 index 000000000..27820a179 --- /dev/null +++ b/tags/pagermod/index.html @@ -0,0 +1,12 @@ +PagerMod | 流动 +
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高. +问题表现 7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。 +偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。 +...

      2022-08-20 · 2 min · 886 words · Liudon
      \ No newline at end of file diff --git a/tags/pagermod/index.xml b/tags/pagermod/index.xml new file mode 100644 index 000000000..4e97f97a0 --- /dev/null +++ b/tags/pagermod/index.xml @@ -0,0 +1,253 @@ + + + + PagerMod on 流动 + https://liudon.com/tags/pagermod/ + Recent content in PagerMod on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 24 Aug 2022 12:37:22 +0800 + + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="../../posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 优化博客的累计布局偏移(CLS)问题 + https://liudon.com/posts/fix-blog-cls/ + Sat, 20 Aug 2022 07:27:22 +0800 + https://liudon.com/posts/fix-blog-cls/ + <p>此文已过期,优化方案参考<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>.</p> +<h4 id="问题表现">问题表现</h4> +<p>7月份将博客部署由<code>Github</code>迁移到<code>Cloudflare</code>后,开始关注博客的性能问题。</p> +<p>偶然看到苏卡卡大佬的<a href="https://blog.skk.moe/post/fix-blog-cls/">CLS优化文章</a>,拿自己博客也测试了下,发现也存在同样的问题。</p> + 此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      +

      问题表现

      +

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      +

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      +

      + +Lighthouse测试报告 + + +

      +

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      +

      为了解决这个问题,需要指定封面的宽高参数。

      +

      + +cover.html code + + +

      +

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      +

      解决方案

      +
        +
      1. +

        新增封面配置

        +

        文章封面配置新增widthheight属性。

        +
        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. +
      3. +

        自定义封面文件

        +

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

        +
            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        +

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        +

        + +cos-img-process + + +

        +
      4. +
      5. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      6. +
      +

      再进一步

      +

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      +

      基于markdown语法的图片代码,是不支持宽高参数的。

      +

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      +

      + +figure.html code + + +

      +

      我们使用figure语法插入图片,指定图片宽高。

      +

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

      +
          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      +
      +

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      +
      +

      + +gtmetrix-result + + +

      +

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      +]]>
      +
      +
      +
      diff --git a/tags/pagermod/page/1/index.html b/tags/pagermod/page/1/index.html new file mode 100644 index 000000000..9a78880ee --- /dev/null +++ b/tags/pagermod/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/pagermod/ + \ No newline at end of file diff --git a/tags/php/index.html b/tags/php/index.html new file mode 100644 index 000000000..daa2790bf --- /dev/null +++ b/tags/php/index.html @@ -0,0 +1,11 @@ +Php | 流动 +

      PHP7.2编译安装后没有php.ini文件的问题

      下载PHP7.2源码,编译安装。 +[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies [root@VM_73_135_centos ~/swoole-src-4.4.12]# 安装Swoole。 +phpize && \ ./configure && \ make && make install 安装完,准备修改php.ini文件,结果没找到。 +...

      2019-11-26 · 1 min · 241 words · Liudon

      一个Curl的耗时长的问题

      发现某个接口请求很慢,但是后端确认接口是很快的。 +在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。 +业务里用到了Requests这个库,一开始以为是这个库导致的问题。 +...

      2019-09-04 · 2 min · 925 words · Liudon
      \ No newline at end of file diff --git a/tags/php/index.xml b/tags/php/index.xml new file mode 100644 index 000000000..e3958f915 --- /dev/null +++ b/tags/php/index.xml @@ -0,0 +1,163 @@ + + + + Php on 流动 + https://liudon.com/tags/php/ + Recent content in Php on 流动 + Hugo -- 0.134.3 + zh-cn + Tue, 26 Nov 2019 19:56:18 +0800 + + + PHP7.2编译安装后没有php.ini文件的问题 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + Tue, 26 Nov 2019 19:56:18 +0800 + https://liudon.com/posts/php7.2%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%90%8E%E6%B2%A1%E6%9C%89php.ini%E6%96%87%E4%BB%B6/ + <p>下载PHP7.2源码,编译安装。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v +</span></span><span style="display:flex;"><span>PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS ) +</span></span><span style="display:flex;"><span>Copyright (c) 1997-2018 The PHP Group +</span></span><span style="display:flex;"><span>Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies +</span></span><span style="display:flex;"><span>[root@VM_73_135_centos ~/swoole-src-4.4.12]# +</span></span></code></pre></div><p>安装Swoole。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>phpize &amp;&amp; \ +</span></span><span style="display:flex;"><span>./configure &amp;&amp; \ +</span></span><span style="display:flex;"><span>make &amp;&amp; make install +</span></span></code></pre></div><p>安装完,准备修改<code>php.ini</code>文件,结果没找到。</p> + 下载PHP7.2源码,编译安装。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -v
      +PHP 7.2.25 (cli) (built: Nov 26 2019 19:33:23) ( NTS )
      +Copyright (c) 1997-2018 The PHP Group
      +Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]# 
      +

      安装Swoole。

      +
      phpize && \
      +./configure && \
      +make && make install
      +

      安装完,准备修改php.ini文件,结果没找到。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll /usr/local/services/php/etc/
      +total 88
      +-rw-r--r-- 1 root root  1364 Nov 26 19:34 pear.conf
      +-rw-r--r-- 1 root root  4508 Nov 26 19:34 php-fpm.conf.default
      +drwxr-xr-x 2 root root  4096 Nov 26 19:34 php-fpm.d
      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => (none)
      +

      这是什么鬼,居然没有php.ini文件。

      +

      原来PHP源码里提供了两个php.ini文件,你需要按需拷贝到你的PHP的目录下。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# ll ../php-7.2.25 | grep ini
      +-rw-rw-r--  1 root root   71232 Nov 20 23:11 php.ini-development
      +-rw-rw-r--  1 root root   71413 Nov 20 23:11 php.ini-production
      +

      拷贝后。

      +
      [root@VM_73_135_centos ~/swoole-src-4.4.12]# php -i | grep "Loaded Confi"
      +Loaded Configuration File => /usr/local/services/php/etc/php.ini
      +[root@VM_73_135_centos ~/swoole-src-4.4.12]#
      +
      ]]>
      +
      + + 一个Curl的耗时长的问题 + https://liudon.com/posts/curl-cost-time-long/ + Wed, 04 Sep 2019 11:07:46 +0800 + https://liudon.com/posts/curl-cost-time-long/ + <p>发现某个接口请求很慢,但是后端确认接口是很快的。</p> +<p>在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。</p> +<p>业务里用到了<code>Requests</code>这个库,一开始以为是这个库导致的问题。</p> + 发现某个接口请求很慢,但是后端确认接口是很快的。

      +

      在机器上通过shell执行curl命令,确实很快,但是PHP代码里请求又确实很慢。

      +

      业务里用到了Requests这个库,一开始以为是这个库导致的问题。

      +

      Requests_Transport_cURL类里断点定位了下,确实很慢,curl_getinfo返回的信息如下。

      +
      array (
      +  'url' => 'http://xxxxx',
      +  'content_type' => 'text/html',
      +  'http_code' => 200,
      +  'header_size' => 64,
      +  'request_size' => 305,
      +  'filetime' => -1,
      +  'ssl_verify_result' => 0,
      +  'redirect_count' => 0,
      +  'total_time' => 2.074094,
      +  'namelookup_time' => 2.5E-5,
      +  'connect_time' => 0.032107,
      +  'pretransfer_time' => 0.032109,
      +  'size_upload' => 186,
      +  'size_download' => 99,
      +  'speed_download' => 47,
      +  'speed_upload' => 89,
      +  'download_content_length' => 99,
      +  'upload_content_length' => 186,
      +  'starttransfer_time' => 2.032866,
      +  'redirect_time' => 0,
      +  'certinfo' =>
      +  array (
      +  ),
      +)
      +

      这里可以看到starttransfer_time时间很长。

      +

      搜索了一番,发现网上一个case,cURL slow starttransfer_time

      +

      里面提供了Expect: 100-continue这个header,又搜索了一番这个header资料。

      +

      curl在发POST请求的时候,如果body大于1k:

      +
        +
      1. 先追加一个Expect: 100-continue请求头信息,发送这个不包含 POST 数据的请求;
      2. +
      3. 如果服务器返回的响应头信息中包含Expect: 100-continue,则表示 Server 愿意接受数据,这时才 POST 真正数据给 Server; +如果等待1s,没有收到服务器肯定或否定的应答,那么继续发起POST请求,这种会导致请求耗时变长。
      4. +
      +

      在机器上抓了个包,执行下面命令。

      +
      注意,下面port后面的80改成实际的端口
      +
      +tcpdump -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
      +

      拿到的包信息。

      +
      09:17:19.421587 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 767181008:767181314, ack 353986709, win 115, options [nop,nop,TS val 2628114858 ecr 1424896084], length 306
      +E..f.m@.@...d}@.        A...XF.-.@...h....s.......
      +....T.0TPOST /cgi HTTP/1.1
      +User-Agent: php-requests/1.6
      +Accept: */*
      +Accept-Encoding: deflate, gzip
      +Referer: http://xxxxx:12345/cgi
      +Content-Length: 188
      +Expect: 100-continue
      +Content-Type: multipart/form-data; boundary=----------------------------ee2f4d848646
      +
      +
      +09:17:21.421786 IP xxx.54360 > xxxxx:12345: Flags [P.], seq 306:494, ack 1, win 115, options [nop,nop,TS val 2628115359 ecr 1424896091], length 188
      +E....n@.@..Md}@.        A...XF.-.B...h....s./.....
      +....T.0[------------------------------ee2f4d848646
      +Content-Disposition: form-data; name="req"
      +
      +{"command":"zzz","appId":"yyyy"}
      +------------------------------ee2f4d848646--
      +
      +09:17:21.458628 IP xxxxx:12345 > xxx.54360: Flags [P.], seq 1:118, ack 494, win 130, options [nop,nop,TS val 1424896593 ecr 2628115359], length 117
      +E...X.@.5.Q2    A..d}@.F..X..h.-.B......3.....
      +T.2Q....HTTP/1.1 200 OK
      +Content-Type: text/html
      +Content-Length: 53
      +
      +{
      +    "data": [],
      +    "errno": 0,
      +    "error": "ok"
      +}
      +

      可以看到确实是先发了一个100-continue的请求,然后再发的实际POST请求。

      +

      在机器上执行下面的shell命令。

      +
      curl 'http://xxxxx:12345/cgi' -H"Expect: 100-continue" -v
      +

      返回如下,可以看到返回的header头里确实没有Expect这项。

      +
      * About to connect() to xxxxx port 12345 (#0)
      +*   Trying xxxxx...
      +* Connected to xxxxx (xxxxx) port 12345 (#0)
      +> GET /cloud_cgi HTTP/1.1
      +> User-Agent: curl/7.29.0
      +> Host: xxxxx:12345
      +> Accept: */*
      +> Expect: 100-continue
      +> 
      +< HTTP/1.1 200 OK
      +< Content-Type: text/html
      +< Content-Length: 42
      +< 
      +* Connection #0 to host xxxxx left intact
      +{"errno":100,"error":"参数格式错误"}
      +

      解决方法:

      +

      请求的时候,header里新增一项。

      +
      Expect:
      +
      ]]>
      +
      +
      +
      diff --git a/tags/php/page/1/index.html b/tags/php/page/1/index.html new file mode 100644 index 000000000..e5ab1f283 --- /dev/null +++ b/tags/php/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/php/ + \ No newline at end of file diff --git a/tags/protoc/index.html b/tags/protoc/index.html new file mode 100644 index 000000000..1e182fe4c --- /dev/null +++ b/tags/protoc/index.html @@ -0,0 +1,7 @@ +Protoc | 流动 +

      解决 "undeclared name: any (requires version go1.18 or later)" 编译错误

      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ $ protoc-gen-go --version protoc-gen-go v1.34.2 $ $ sh make.sh user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) $ 流水线编译报错,其中make.sh文件代码: +... protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto ... go build 同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。 +...

      2024-06-14 · 1 min · 473 words · Liudon
      \ No newline at end of file diff --git a/tags/protoc/index.xml b/tags/protoc/index.xml new file mode 100644 index 000000000..155557a9d --- /dev/null +++ b/tags/protoc/index.xml @@ -0,0 +1,72 @@ + + + + Protoc on 流动 + https://liudon.com/tags/protoc/ + Recent content in Protoc on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 14 Jun 2024 20:41:20 +0800 + + + 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + Fri, 14 Jun 2024 20:41:20 +0800 + https://liudon.com/posts/solving-undeclared-name-any-requires-version-go1.18-or-later-compilation-error/ + <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ protoc-gen-go --version +</span></span><span style="display:flex;"><span>protoc-gen-go v1.34.2 +</span></span><span style="display:flex;"><span>$ +</span></span><span style="display:flex;"><span>$ sh make.sh +</span></span><span style="display:flex;"><span>user.pb.go:123:45: undeclared name: any (requires version go1.18 or later) +</span></span><span style="display:flex;"><span>$ +</span></span></code></pre></div><p>流水线编译报错,其中<code>make.sh</code>文件代码:</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>... +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>go build +</span></span></code></pre></div><p>同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。</p> +
      $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      +$ 
      +$ protoc-gen-go --version
      +protoc-gen-go v1.34.2
      +$ 
      +$ sh make.sh
      +user.pb.go:123:45: undeclared name: any (requires version go1.18 or later)
      +$ 
      +

      流水线编译报错,其中make.sh文件代码:

      +
      ...
      +
      +protoc -I=./ --proto_path=./ --go_out=./ --go_opt=paths=source_relative user.proto 
      +
      +...
      +
      +go build
      +

      同样的代码在本机编译就没问题,但是放到流水线里编译就报上面的错误。

      +

      登到流水线编译机器上,看了下go的版本已经是1.18.1了,按理不应该报这个错误的。

      +

      关键之前流水线编译是没问题的,怀疑是开发分支代码的问题,用master分支编译了一下,也还是报这个错误。

      +

      手动执行make.sh里的每条命令,发现是protoc编译pb文件时报的这个错误。

      +

      经过一番查找后,发现是protoc-gen-go在4月份更新了版本,引入了新特性。

      +

      protoc-gen-go’s versions

      +
      Versions in this module
      +v1
      +    v1.34.2 Jun 11, 2024
      +    v1.34.1 May 6, 2024
      +    v1.34.0 Apr 30, 2024
      +    v1.33.0 Mar 5, 2024
      +    v1.32.0 Dec 22, 2023
      +

      Protobuf Editions Overview

      +
      +

      Protobuf Editions replace the proto2 and proto3 designations that we have used for Protocol Buffers. Instead of adding syntax = “proto2” or syntax = “proto3” at the top of proto definition files, you use an edition number, such as edition = “2024”, to specify the default behaviors your file will have. Editions enable the language to evolve incrementally over time.

      +

      Instead of the hardcoded behaviors that older versions have had, editions represent a collection of features with a default value (behavior) per feature. Features are options on a file, message, field, enum, and so on, that specify the behavior of protoc, the code generators, and protobuf runtimes. You can explicitly override a behavior at those different levels (file, message, field, …) when your needs don’t match the default behavior for the edition you’ve selected. You can also override your overrides. The section later in this topic on lexical scoping goes into more detail on that.

      +
      +

      改用历史版本后解决。

      +
      go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33.0
      +
      ]]>
      +
      +
      +
      diff --git a/tags/protoc/page/1/index.html b/tags/protoc/page/1/index.html new file mode 100644 index 000000000..76ef012e7 --- /dev/null +++ b/tags/protoc/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/protoc/ + \ No newline at end of file diff --git a/tags/sqlcommenter/index.html b/tags/sqlcommenter/index.html new file mode 100644 index 000000000..5bfba86fc --- /dev/null +++ b/tags/sqlcommenter/index.html @@ -0,0 +1,7 @@ +Sqlcommenter | 流动 +

      GORM增加sqlcommenter特性

      什么是sqlcommenter? +sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side. +...

      2024-04-18 · 2 min · 742 words · Liudon
      \ No newline at end of file diff --git a/tags/sqlcommenter/index.xml b/tags/sqlcommenter/index.xml new file mode 100644 index 000000000..7e6b0d828 --- /dev/null +++ b/tags/sqlcommenter/index.xml @@ -0,0 +1,181 @@ + + + + Sqlcommenter on 流动 + https://liudon.com/tags/sqlcommenter/ + Recent content in Sqlcommenter on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 18 Apr 2024 21:25:24 +0800 + + + GORM增加sqlcommenter特性 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + Thu, 18 Apr 2024 21:25:24 +0800 + https://liudon.com/posts/gorm-supports-sqlcommenter/ + <p>什么是sqlcommenter?</p> +<blockquote> +<p>sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.</p> + 什么是sqlcommenter?

      +
      +

      sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

      +
      +

      GORM提供了hints组件,可以支持sqlcommenter

      +
      import "gorm.io/hints"
      +
      +DB.Clauses(hints.Comment("select", "master")).Find(&User{})
      +// SELECT /*master*/ * FROM `users`;
      +
      +DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
      +// /*node2*/ INSERT INTO `users` ...;
      +
      +DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
      +// SELECT * FROM `users` WHERE id = ? /* hint */
      +

      但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

      +

      我希望是全局增加sqlcommenter,业务侧不需要过多调整。

      +

      完整代码如下:

      +
      plugins/gorm.go
      +
      +package plugins
      +
      +import (
      +	"fmt"
      +
      +	gorm "gorm.io/gorm"
      +	gormclause "gorm.io/gorm/clause"
      +)
      +
      +type Comment struct {
      +	Content string
      +}
      +
      +func (c Comment) Name() string {
      +	return "COMMENT"
      +}
      +
      +func (c Comment) Build(builder gormclause.Builder) {
      +	builder.WriteString("/* ")
      +	builder.WriteString(c.Content)
      +	builder.WriteString(" */")
      +}
      +
      +func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
      +}
      +
      +func (c Comment) ModifyStatement(stmt *gorm.Statement) {
      +	clause := stmt.Clauses[c.Name()]
      +    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
      +    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
      +	clause.Expression = c
      +	stmt.Clauses[c.Name()] = clause
      +}
      +
      +var extraClause = []string{"COMMENT"}
      +
      +type CommentClausePlugin struct{}
      +
      +// NewCommentClausePlugin create a new ExtraPlugin
      +func NewCommentClausePlugin() *CommentClausePlugin {
      +	return &CommentClausePlugin{}
      +}
      +
      +// Name plugin name
      +func (ep *CommentClausePlugin) Name() string {
      +	return "CommentClausePlugin"
      +}
      +
      +// Initialize register BuildClauses
      +func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
      +	initClauses(db)
      +	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
      +	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)
      +
      +	return
      +}
      +
      +func AddAnnotation(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +
      +	rid := "xx"
      +	// context上下文里取rid信息
      +	if v, ok := db.Statement.Context.Value("rid").(string); ok {
      +		rid = v
      +	}
      +
      +	content := fmt.Sprintf("rid=%s", rid)
      +
      +	if db.Statement.SQL.Len() > 0 {
      +		oldSQL := db.Statement.SQL.String()
      +		db.Statement.SQL.Reset()
      +		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
      +		return
      +	}
      +
      +	db.Statement.AddClause(Comment{Content: content})
      +}
      +
      +// initClauses init SQL clause
      +func initClauses(db *gorm.DB) {
      +	if db.Error != nil {
      +		return
      +	}
      +	createClause := append(extraClause, db.Callback().Create().Clauses...)
      +	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
      +	queryClause := append(extraClause, db.Callback().Query().Clauses...)
      +	updateClause := append(extraClause, db.Callback().Update().Clauses...)
      +	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
      +	rowClause := append(extraClause, db.Callback().Row().Clauses...)
      +	db.Callback().Create().Clauses = createClause
      +	db.Callback().Delete().Clauses = deleteClause
      +	db.Statement.Callback().Query().Clauses = queryClause
      +	db.Callback().Update().Clauses = updateClause
      +	db.Callback().Raw().Clauses = rawClause
      +	db.Callback().Row().Clauses = rowClause
      +}
      +
      +
      +main.go
      +package main
      +
      +import (
      +    "context"
      +    "plugins"
      +
      +    gorm "gorm.io/gorm"
      +    "github.com/google/uuid"
      +)
      +
      +
      +type Product struct {
      +  gorm.Model
      +  Code  string
      +  Price uint
      +}
      +
      +func main() {
      +    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
      +    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
      +
      +    db.Use(plugins.NewCommentClausePlugin())
      +
      +    db.Create(&Product{Code: "D42", Price: 100})
      +
      +    // 传入context,指定rid
      +    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
      +    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
      +}
      +

      阻塞了两天的问题,终于解决了!😁😁😁

      +

      how gorm generates sql

      +]]>
      +
      +
      +
      diff --git a/tags/sqlcommenter/page/1/index.html b/tags/sqlcommenter/page/1/index.html new file mode 100644 index 000000000..d5646499d --- /dev/null +++ b/tags/sqlcommenter/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/sqlcommenter/ + \ No newline at end of file diff --git a/tags/ssd/index.html b/tags/ssd/index.html new file mode 100644 index 000000000..46036ad65 --- /dev/null +++ b/tags/ssd/index.html @@ -0,0 +1,8 @@ +SSD | 流动 +

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git a/tags/ssd/index.xml b/tags/ssd/index.xml new file mode 100644 index 000000000..177883a31 --- /dev/null +++ b/tags/ssd/index.xml @@ -0,0 +1,79 @@ + + + + SSD on 流动 + https://liudon.com/tags/ssd/ + Recent content in SSD on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 22 Apr 2022 08:04:18 +0800 + + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      +
      +
      diff --git a/tags/ssd/page/1/index.html b/tags/ssd/page/1/index.html new file mode 100644 index 000000000..ba11099ec --- /dev/null +++ b/tags/ssd/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/ssd/ + \ No newline at end of file diff --git a/tags/ssl/index.html b/tags/ssl/index.html new file mode 100644 index 000000000..4f8cbe627 --- /dev/null +++ b/tags/ssl/index.html @@ -0,0 +1,7 @@ +Ssl | 流动 +

      检测网站支持的SSL/TLS协议版本

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。 +为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。 +...

      2019-11-14 · 1 min · 205 words · Liudon
      \ No newline at end of file diff --git a/tags/ssl/index.xml b/tags/ssl/index.xml new file mode 100644 index 000000000..8a97e54aa --- /dev/null +++ b/tags/ssl/index.xml @@ -0,0 +1,42 @@ + + + + Ssl on 流动 + https://liudon.com/tags/ssl/ + Recent content in Ssl on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 14 Nov 2019 14:48:08 +0800 + + + 检测网站支持的SSL/TLS协议版本 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + Thu, 14 Nov 2019 14:48:08 +0800 + https://liudon.com/posts/how-to-check-what-ssl-tls-versions-are-available-for-a-website/ + <blockquote> +<p>Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。</p> +</blockquote> +<p>为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。</p> + +

      Chrome 72及以上版本不支持TLS 1.0和TLS 1.1,访问TLS 1.0或1.1证书的站点会告警,但不阻止用户访问站点。

      + +

      为了解决Chrome的这个问题,今天升级了下Nginx的TLS协议版本,这里记录一下如何检测支持的协议版本。

      +
        +
      1. +

        检测是否支持TLSv1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1
        +
      2. +
      3. +

        检测是否支持TLSv1.1

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_1
        +
      4. +
      5. +

        检测是否支持TLSv1.2

        +
        openssl s_client -connect [ip or 域名]:443 -tls1_2
        +
      6. +
      +

      参考资料:How to check what SSL/TLS versions are available for a website?

      +]]>
      +
      +
      +
      diff --git a/tags/ssl/page/1/index.html b/tags/ssl/page/1/index.html new file mode 100644 index 000000000..ab262f5d5 --- /dev/null +++ b/tags/ssl/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/ssl/ + \ No newline at end of file diff --git a/tags/swoft/index.html b/tags/swoft/index.html new file mode 100644 index 000000000..60a50da6d --- /dev/null +++ b/tags/swoft/index.html @@ -0,0 +1,19 @@ +Swoft | 流动 +

      Swoft 框架运行分析(五) —— ConsoleProcessor模块分析

      这里以Swoft启动http server为例。 +php bin/swoft http:start +执行上述命令,启动http server。 +在前面第一篇文章的时候,提到了如何启动http服务。 +今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。 +...

      2019-09-26 · 10 min · 4524 words · Liudon

      Swoft 框架运行分析(四) —— EventProcessor模块分析

      今天我们来看一下EventProcessor的实现。 +/** * Handle event register * @return bool */ public function handle(): bool { if (!$this->application->beforeEvent()) { CLog::warning('Stop event processor by beforeEvent return false'); return false; } /** @var EventManager $eventManager */ $eventManager = bean('eventManager'); [$count1, $count2] = ListenerRegister::register($eventManager); CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2); // Trigger a app init event Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); return $this->application->afterEvent(); } 获取eventManager的Bean实例,对应为Swoft\Event\Manager\EventManager类。 +...

      2019-09-26 · 4 min · 1896 words · Liudon

      Swoft 框架运行分析(三) —— BeanProcessor模块分析

      今天讲一下BeanProcessor模块,先看一下handle方法实现。 +/** * Handle bean * * @return bool * @throws ReflectionException * @throws AnnotationException */ public function handle(): bool { if (!$this->application->beforeBean()) { return false; } $handler = new BeanHandler(); $definitions = $this->getDefinitions(); $parsers = AnnotationRegister::getParsers(); $annotations = AnnotationRegister::getAnnotations(); BeanFactory::addDefinitions($definitions); BeanFactory::addAnnotations($annotations); BeanFactory::addParsers($parsers); BeanFactory::setHandler($handler); BeanFactory::init(); /* @var Config $config*/ $config = BeanFactory::getBean('config'); CLog::info('config path=%s', $config->getPath()); CLog::info('config env=%s', $config->getEnv()); $stats = BeanFactory::getStats(); CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats)); return $this->application->afterBean(); } 先通过getDefinitions方法获取所有的Bean定义。 +...

      2019-09-02 · 9 min · 4044 words · Liudon

      Swoft 框架运行分析(二) —— AnnotationProcessor模块分析

      上一篇介绍了,SwoftApplication里定义了6个Processor对象。 +protected function processors(): array { return [ new EnvProcessor($this), new ConfigProcessor($this), new AnnotationProcessor($this), new BeanProcessor($this), new EventProcessor($this), new ConsoleProcessor($this), ]; } 所有的Processor实现都在framework\src\Processor目录下。 +...

      2019-08-29 · 4 min · 1656 words · Liudon

      Swoft 框架运行分析(一)

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。 +以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。 +刚开始看的时候,感觉自己像个原始人,完全看不懂。 +...

      2019-08-29 · 2 min · 966 words · Liudon
      \ No newline at end of file diff --git a/tags/swoft/index.xml b/tags/swoft/index.xml new file mode 100644 index 000000000..a859207f2 --- /dev/null +++ b/tags/swoft/index.xml @@ -0,0 +1,3065 @@ + + + + Swoft on 流动 + https://liudon.com/tags/swoft/ + Recent content in Swoft on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 26 Sep 2019 13:14:23 +0800 + + + Swoft 框架运行分析(五) —— ConsoleProcessor模块分析 + https://liudon.com/posts/swoft-console-processor-analysis/ + Thu, 26 Sep 2019 13:14:23 +0800 + https://liudon.com/posts/swoft-console-processor-analysis/ + <blockquote> +<p>这里以Swoft启动http server为例。</p> +<p>php bin/swoft http:start</p> +<p>执行上述命令,启动http server。</p> +</blockquote> +<p>在前面第一篇文章的时候,提到了如何启动http服务。</p> +<p>今天我们就来看一下http服务是如何启动的,具体实现就在<code>ConsoleProcess</code>这个模块。</p> + +

      这里以Swoft启动http server为例。

      +

      php bin/swoft http:start

      +

      执行上述命令,启动http server。

      + +

      在前面第一篇文章的时候,提到了如何启动http服务。

      +

      今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。

      +
      /**
      +    * Handle console
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeConsole()) {
      +        return false;
      +    }
      +
      +    /** @var Router $router */
      +    $router = bean('cliRouter');
      +
      +    // Register console routes
      +    CommandRegister::register($router);
      +
      +    CLog::info(
      +        'Console command route registered (group %d, command %d)',
      +        $router->groupCount(),
      +        $router->count()
      +    );
      +
      +    // Run console application
      +    bean('cliApp')->run();
      +
      +    return $this->application->afterConsole();
      +}
      +

      这里调用了bean方法获取Bean实例,定义见swoft-component-2.0.5\src\bean\src\Helper\Functions.php

      +
      if (!function_exists('bean')) {
      +    /**
      +     * Get bean by name
      +     *
      +     * @param string $name Bean name Or alias Or class name
      +     *
      +     * @return object|string|mixed
      +     */
      +    function bean(string $name)
      +    {
      +        if (BeanFactory::isSingleton('config')) {
      +            return BeanFactory::getBean($name);
      +        }
      +
      +        return sprintf('${%s}', $name);
      +    }
      +}
      +

      这里调用了BeanFactorygetBean方法。

      +
      /**
      +    * Get object by name
      +    *
      +    * @param string $name Bean name Or alias Or class name
      +    *
      +    * @return object|mixed
      +    */
      +public static function getBean(string $name)
      +{
      +    return Container::getInstance()->get($name);
      +}
      +

      最终调用的是Swoft\Bean\Container下的get方法。

      +
      /**
      +    * Finds an entry of the container by its identifier and returns it.
      +    *
      +    * @param string $id Bean name Or alias Or class name
      +    *
      +    * When class name will return all of instance for class name
      +    *
      +    * @return object
      +    * @throws InvalidArgumentException
      +    */
      +public function get($id)
      +{
      +    // It is singleton
      +    if (isset($this->singletonPool[$id])) {
      +        return $this->singletonPool[$id];
      +    }
      +
      +    // Prototype by clone
      +    if (isset($this->prototypePool[$id])) {
      +        return clone $this->prototypePool[$id];
      +    }
      +
      +    // Alias name
      +    $aliasId = $this->aliases[$id] ?? '';
      +    if ($aliasId) {
      +        return $this->get($aliasId);
      +    }
      +
      +    // Class name
      +    $classNames = $this->classNames[$id] ?? [];
      +    if ($classNames) {
      +        $id = end($classNames);
      +        return $this->get($id);
      +    }
      +
      +    // Interface
      +    if (interface_exists($id)) {
      +        $id = InterfaceRegister::getInterfaceInjectBean($id);
      +        return $this->get($id);
      +    }
      +
      +    // Not defined
      +    if (!isset($this->objectDefinitions[$id])) {
      +        throw new InvalidArgumentException(sprintf('The bean of %s is not defined', $id));
      +    }
      +
      +    /* @var ObjectDefinition $objectDefinition */
      +    $objectDefinition = $this->objectDefinitions[$id];
      +
      +    // Prototype
      +    return $this->safeNewBean($objectDefinition->getName());
      +}
      +

      获取对应的ObjectDefinition实例,然后调用safeNewBean方法。

      +
      /**
      +    * Secure creation of beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object|mixed
      +    */
      +private function safeNewBean(string $beanName, string $id = '')
      +{
      +    try {
      +        return $this->newBean($beanName, $id);
      +    } catch (Throwable $e) {
      +        throw new InvalidArgumentException($e->getMessage(), 500, $e);
      +    }
      +}
      +

      这里又调用了newBean方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的Bean类。

      +

      cliRouter对应的类是说明呢?这个定义在swoft-component-2.0.5\src\console\src\AutoLoader.php里。

      +
      /**
      +    * {@inheritDoc}
      +    */
      +public function beans(): array
      +{
      +    return [
      +        'cliApp'    => [
      +            'class'   => Application::class,
      +            'version' => '2.0.0'
      +        ],
      +        'cliRouter' => [
      +            'class' => Router::class,
      +        ],
      +        'cliDispatcher' => [
      +            'class' => ConsoleDispatcher::class,
      +        ],
      +    ];
      +}
      +

      所以$router = bean('cliRouter'),返回的是一个Swoft\Console\Router\Router类。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CommandRegister::register($router);
      +

      调用了CommandRegister类的register方法。

      +
      
      +/**
      +    * @param Router $router
      +    * @throws ReflectionException
      +    */
      +public static function register(Router $router): void
      +{
      +    $maxLen  = 12;
      +    $groups  = [];
      +    $docOpts = [
      +        'allow' => ['example']
      +    ];
      +    $defInfo = [
      +        'example'     => '',
      +        'description' => 'No description message',
      +    ];
      +
      +    foreach (self::$commands as $class => $mapping) {
      +        $names = [];
      +        $group = $mapping['group'];
      +        // Set ID aliases
      +        $router->setIdAliases($mapping['idAliases']);
      +        // Set group name aliases
      +        $router->setGroupAliases($group, $mapping['aliases']);
      +
      +        $refInfo = Swoft::getReflection($class);
      +        $mhdInfo = $refInfo['methods'] ?? [];
      +        $grpOpts = $mapping['options'] ?? [];
      +
      +        foreach ($mapping['commands'] as $method => $route) {
      +            // $method = $route['method'];
      +            $cmdDesc = $route['desc'];
      +            $command = $route['command'];
      +
      +            $idLen = strlen($group . $command);
      +            if ($idLen > $maxLen) {
      +                $maxLen = $idLen;
      +            }
      +
      +            $cmdExam = '';
      +            if (!empty($mhdInfo[$method]['comments'])) {
      +                $tagInfo = DocBlock::getTags($mhdInfo[$method]['comments'], $docOpts, $defInfo);
      +                $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo['description']);
      +                $cmdExam = $tagInfo['example'];
      +            }
      +
      +            $route['group']   = $group;
      +            $route['desc']    = ucfirst($cmdDesc);
      +            $route['example'] = $cmdExam;
      +            $route['options'] = self::mergeOptions($grpOpts, $route['options']);
      +            // Append group option
      +            $route['enabled']   = $mapping['enabled'];
      +            $route['coroutine'] = $mapping['coroutine'];
      +
      +            $router->map($group, $command, [$class, $method], $route);
      +            $names[] = $command;
      +        }
      +
      +        $groupExam = '';
      +        $groupDesc = $mapping['desc'];
      +        if (!empty($refInfo['comments'])) {
      +            $tagInfo   = DocBlock::getTags($refInfo['comments'], $docOpts, $defInfo);
      +            $groupDesc = $groupDesc ?: Str::firstLine($tagInfo['description']);
      +            $groupExam = $tagInfo['example'];
      +        }
      +
      +        $groups[$group] = [
      +            'names'   => $names,
      +            'desc'    => ucfirst($groupDesc),
      +            'class'   => $class,
      +            'alias'   => $mapping['alias'],
      +            'aliases' => $mapping['aliases'],
      +            'example' => $groupExam,
      +        ];
      +    }
      +
      +    $router->setGroups($groups);
      +    // +1 because router->delimiter
      +    $router->setKeyWidth($maxLen + 1);
      +    // clear data
      +    self::$commands = [];
      +}
      +

      这里遍历了类属性$commands注册路由。

      +

      那么$commands这个属性是哪里来的呢?

      +

      既然开头我们说的是http服务是怎么启动的,这里我们就以http-server来举例,找到swoft-component-2.0.5\src\http-server\src\Command\HttpServerCommand.php文件。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server\Command;
      +
      +use ReflectionException;
      +use Swoft;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Console\Annotation\Mapping\Command;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\Annotation\Mapping\CommandOption;
      +use Swoft\Console\Helper\Show;
      +use Swoft\Http\Server\HttpServer;
      +use Swoft\Server\Command\BaseServerCommand;
      +use Swoft\Server\Exception\ServerException;
      +use function bean;
      +use function input;
      +use function output;
      +
      +/**
      + * Provide some commands to manage the swoft HTTP server
      + *
      + * @since 2.0
      + *
      + * @Command("http", alias="httpsrv", coroutine=false)
      + * @example
      + *  {fullCmd}:start     Start the http server
      + *  {fullCmd}:stop      Stop the http server
      + */
      +class HttpServerCommand extends BaseServerCommand
      +{
      +    /**
      +     * Start the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +     * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @throws ServerException
      +     * @example
      +     *   {fullCommand}
      +     *   {fullCommand} -d
      +     *
      +     */
      +    public function start(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $masterPid = $server->getPid();
      +            output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +            return;
      +        }
      +
      +        // Startup settings
      +        $this->configStartOption($server);
      +
      +        $settings = $server->getSetting();
      +        // Setting
      +        $workerNum = $settings['worker_num'];
      +
      +        // Server startup parameters
      +        $mainHost = $server->getHost();
      +        $mainPort = $server->getPort();
      +        $modeName = $server->getModeName();
      +        $typeName = $server->getTypeName();
      +
      +        // Http
      +        $panel = [
      +            'HTTP' => [
      +                'listen' => $mainHost . ':' . $mainPort,
      +                'type'   => $typeName,
      +                'mode'   => $modeName,
      +                'worker' => $workerNum,
      +            ],
      +        ];
      +
      +        // Port Listeners
      +        $panel = $this->appendPortsToPanel($server, $panel);
      +
      +        Show::panel($panel);
      +
      +        output()->writeln('<success>HTTP server start success !</success>');
      +
      +        // Start the server
      +        $server->start();
      +    }
      +
      +    /**
      +     * Reload worker processes
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-t]")
      +     * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function reload(): void
      +    {
      +        $server = $this->createServer();
      +        $script = input()->getScript();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot reload</error>');
      +            return;
      +        }
      +
      +        output()->writef('<info>Server %s is reloading</info>', $script);
      +
      +        if ($reloadTask = input()->hasOpt('t')) {
      +            Show::notice('Will only reload task worker');
      +        }
      +
      +        if (!$server->reload($reloadTask)) {
      +            Show::error('The swoole server worker process reload fail!');
      +            return;
      +        }
      +
      +        output()->writef('<success>HTTP server %s reload success</success>', $script);
      +    }
      +
      +    /**
      +     * Stop the currently running server
      +     *
      +     * @CommandMapping()
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function stop(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if (!$server->isRunning()) {
      +            output()->writeln('<error>The HTTP server is not running! cannot stop.</error>');
      +            return;
      +        }
      +
      +        // Do stopping.
      +        $server->stop();
      +    }
      +
      +    /**
      +     * Restart the http server
      +     *
      +     * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
      +     * @CommandOption("daemon", short="d", desc="Run server on the background")
      +     *
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     * @example
      +     *  {fullCommand}
      +     *  {fullCommand} -d
      +     */
      +    public function restart(): void
      +    {
      +        $server = $this->createServer();
      +
      +        // Check if it has started
      +        if ($server->isRunning()) {
      +            $success = $server->stop();
      +
      +            if (!$success) {
      +                output()->error('Stop the old server failed!');
      +                return;
      +            }
      +        }
      +
      +        output()->writef('<success>Server HTTP restart success !</success>');
      +        $server->startWithDaemonize();
      +    }
      +
      +    /**
      +     * @return HttpServer
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    private function createServer(): HttpServer
      +    {
      +        $script  = input()->getScript();
      +        $command = $this->getFullCommand();
      +
      +        /** @var HttpServer $server */
      +        $server = bean('httpServer');
      +        $server->setScriptFile(Swoft::app()->getPath($script));
      +        $server->setFullCommand($command);
      +
      +        return $server;
      +    }
      +}
      +

      通过Swoft文档,我们可以看到这里分别使用了类注解和方法注解。

      +
      @Command("http", alias="httpsrv", coroutine=false)
      +@CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +@CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +...
      +

      通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。

      +

      这里以Swoft\Console\Annotation\Mapping\CommandMapping这个注解为例,对应的注解解析类为Swoft\Console\Annotation\Parser\CommandMappingParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Console\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Console\Annotation\Mapping\CommandMapping;
      +use Swoft\Console\CommandRegister;
      +
      +/**
      + * Class CommandMappingParser
      + *
      + * @since 2.0
      + * @AnnotationParser(CommandMapping::class)
      + */
      +class CommandMappingParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int            $type Class or Method or Property
      +     * @param CommandMapping $annotation Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_METHOD) {
      +            throw new AnnotationException('`@CommandMapping` must be defined on class method!');
      +        }
      +
      +        $method = $this->methodName;
      +
      +        // add route info for controller action
      +        CommandRegister::addRoute($this->className, $method, [
      +            'command' => $annotation->getName() ?: $method,
      +            'method'  => $method,
      +            'alias'   => $annotation->getAlias(),
      +            'aliases' => $annotation->getAliases(),
      +            'desc'    => $annotation->getDesc(),
      +            'usage'   => $annotation->getUsage(),
      +            // 'example' => $annotation->getExample(),
      +        ]);
      +
      +        return [];
      +    }
      +}
      +

      看到这里,你应该可以猜到CommandRegister类的$commands是怎么来的了吧。

      +

      我们看下CommandRegister类的addRoute方法,验证下想法。

      +
      /**
      +    * @param string $class
      +    * @param string $method
      +    * @param array  $route
      +    *
      +    * @throws AnnotationException
      +    */
      +public static function addRoute(string $class, string $method, array $route): void
      +{
      +    self::checkClass($class);
      +
      +    // init some keys
      +    $route['options']   = [];
      +    $route['arguments'] = [];
      +    // save
      +    self::$commands[$class]['commands'][$method] = $route;
      +}
      +

      bingo,跟我们猜想的一模一样,这下我们也知道CommandMapping这个注解是用来注册终端的路由信息。

      +

      回到ConsoleProcessor类,接着看代码。

      +
      CLog::info(
      +    'Console command route registered (group %d, command %d)',
      +    $router->groupCount(),
      +    $router->count()
      +);
      +

      打印日志。

      +
      // Run console application
      +bean('cliApp')->run();
      +

      感觉到了重头戏。

      +

      根据前面的代码,我们知道cliApp这个Bean实例对应的类是Swoft\Console\Application

      +
      /**
      +    * @return void
      +    * @throws ContainerException
      +    */
      +public function run(): void
      +{
      +    try {
      +        Swoft::trigger(ConsoleEvent::RUN_BEFORE, $this);
      +
      +        // Prepare
      +        $this->prepare();
      +
      +        // Get input command
      +        $inputCommand = $this->input->getCommand();
      +
      +        if (!$inputCommand) {
      +            $this->filterSpecialOption();
      +        } else {
      +            $this->doRun($inputCommand);
      +        }
      +
      +        Swoft::trigger(ConsoleEvent::RUN_AFTER, $this, $inputCommand);
      +    } catch (Throwable $e) {
      +        /** @var ConsoleErrorDispatcher $errDispatcher */
      +        $errDispatcher = BeanFactory::getSingleton(ConsoleErrorDispatcher::class);
      +
      +        // Handle request error
      +        $errDispatcher->run($e);
      +    }
      +}
      +

      通过Swoft::trigger,注册了ConsoleEvent::RUN_BEFOREConsoleEvent::RUN_AFTER两个事件。

      +
      protected function prepare(): void
      +{
      +    $this->input  = \input();
      +    $this->output = \output();
      +
      +    // load builtin comments vars
      +    $this->setCommentsVars($this->commentsVars());
      +}
      +

      prepare比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。

      +
      $inputCommand = $this->input->getCommand();
      +if (!$inputCommand) {
      +    $this->filterSpecialOption();
      +} else {
      +    $this->doRun($inputCommand);
      +}
      +

      获取终端命令行下的输入,如果有输入执行doRun方法。

      +
      /**
      +    * @param string $inputCmd
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws Throwable
      +    */
      +protected function doRun(string $inputCmd): void
      +{
      +    $output = $this->output;
      +    /* @var Router $router */
      +    $router = Swoft::getBean('cliRouter');
      +    $result = $router->match($inputCmd);
      +
      +    // Command not found
      +    if ($result[0] === Router::NOT_FOUND) {
      +        $names = $router->getAllNames();
      +        $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +        // find similar command names by similar_text()
      +        if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +            $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +        } else {
      +            $this->showApplicationHelp(false);
      +        }
      +        return;
      +    }
      +
      +    $info = $result[1];
      +
      +    // Only input a group name, display help for the group
      +    if ($result[0] === Router::ONLY_GROUP) {
      +        $this->showGroupHelp($info['group']);
      +        return;
      +    }
      +
      +    // Display help for a command
      +    if ($this->input->getSameOpt(['h', 'help'])) {
      +        $this->showCommandHelp($info);
      +        return;
      +    }
      +
      +    // Parse default options and arguments
      +    $this->bindCommandFlags($info);
      +    $this->input->setCommandId($info['cmdId']);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +
      +    // Call command handler
      +    /** @var ConsoleDispatcher $dispatcher */
      +    $dispatcher = Swoft::getSingleton('cliDispatcher');
      +    $dispatcher->dispatch($info);
      +
      +    Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, $this, $info);
      +}
      +
      $router = Swoft::getBean('cliRouter');
      +$result = $router->match($inputCmd);
      +

      获取cliRouter实例,根据输入匹配路由操作类。

      +
      /**
      +    * Match route by input command
      +    *
      +    * @param array $params [$route]
      +    *
      +    * @return array
      +    *
      +    * [
      +    *  status, info(array)
      +    * ]
      +    */
      +public function match(...$params): array
      +{
      +    $delimiter = $this->delimiter;
      +    $inputCmd  = trim($params[0], "$delimiter ");
      +    $noSepChar = strpos($inputCmd, $delimiter) === false;
      +
      +    // If use command ID alias
      +    if ($noSepChar && isset($this->idAliases[$inputCmd])) {
      +        $inputCmd = $this->idAliases[$inputCmd];
      +        // Must re-check
      +        $noSepChar = strpos($inputCmd, $delimiter) === false;
      +    }
      +
      +    if ($noSepChar && in_array($inputCmd, $this->defaultCommands, true)) {
      +        $group   = $this->defaultGroup;
      +        $command = $this->resolveCommandAlias($inputCmd);
      +
      +        // Only a group name
      +    } elseif ($noSepChar) {
      +        $group = $this->resolveGroupAlias($inputCmd);
      +
      +        if (isset($this->groups[$group])) {
      +            return [self::ONLY_GROUP, ['group' => $group]];
      +        }
      +
      +        return [self::NOT_FOUND];
      +    } else {
      +        $nameList = explode($delimiter, $inputCmd, 2);
      +
      +        if (count($nameList) === 2) {
      +            [$group, $command] = $nameList;
      +            // resolve command alias
      +            $command = $this->resolveCommandAlias($command);
      +        } else {
      +            $command = '';
      +            // $command = $this->defaultCommand;
      +            $group = $nameList[0];
      +        }
      +    }
      +
      +    $group = $this->resolveGroupAlias($group);
      +    // build command ID
      +    $commandID = $this->buildCommandID($group, $command);
      +
      +    if (isset($this->routes[$commandID])) {
      +        $info = $this->routes[$commandID];
      +        // append some info
      +        $info['cmdId'] = $commandID;
      +
      +        return [self::FOUND, $info];
      +    }
      +
      +    if ($group && isset($this->groups[$group])) {
      +        return [self::ONLY_GROUP, ['group' => $group]];
      +    }
      +
      +    return [self::NOT_FOUND];
      +}
      +

      这里会返回匹配后的路由信息。

      +

      回到doRun方法。

      +
      // Command not found
      +if ($result[0] === Router::NOT_FOUND) {
      +    $names = $router->getAllNames();
      +    $output->liteError("The entered command '{$inputCmd}' is not exists!");
      +
      +    // find similar command names by similar_text()
      +    if ($similar = Arr::findSimilar($inputCmd, $names)) {
      +        $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
      +    } else {
      +        $this->showApplicationHelp(false);
      +    }
      +    return;
      +}
      +
      +$info = $result[1];
      +
      +// Only input a group name, display help for the group
      +if ($result[0] === Router::ONLY_GROUP) {
      +    $this->showGroupHelp($info['group']);
      +    return;
      +}
      +
      +// Display help for a command
      +if ($this->input->getSameOpt(['h', 'help'])) {
      +    $this->showCommandHelp($info);
      +    return;
      +}
      +

      根据返回的路由信息进行不同的处理。

      +
      // Parse default options and arguments
      +$this->bindCommandFlags($info);
      +$this->input->setCommandId($info['cmdId']);
      +
      +Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);
      +

      绑定默认参数,注册ConsoleEvent::DISPATCH_BEFORE事件。

      +
      // Call command handler
      +/** @var ConsoleDispatcher $dispatcher */
      +$dispatcher = Swoft::getSingleton('cliDispatcher');
      +$dispatcher->dispatch($info);
      +

      获取cliDispatcherBean实例,对应Swoft\Console\ConsoleDispatcher类,调用dispatch方法。

      +
      /**
      +    * @param array $params
      +    *
      +    * @return void
      +    * @throws ReflectionException
      +    * @throws Throwable
      +    */
      +public function dispatch(...$params): void
      +{
      +    $route = $params[0];
      +    // Handler info
      +    [$className, $method] = $route['handler'];
      +
      +    // Bind method params
      +    $params = $this->getBindParams($className, $method);
      +    $object = Swoft::getSingleton($className);
      +
      +    // Blocking running
      +    if (!$route['coroutine']) {
      +        $this->before(get_parent_class($object), $method);
      +        PhpHelper::call([$object, $method], ...$params);
      +        $this->after($method);
      +        return;
      +    }
      +
      +    // Hook php io function
      +    Runtime::enableCoroutine();
      +
      +    // If in unit test env, has been in coroutine.
      +    if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
      +        $this->executeByCo($object, $method, $params);
      +        return;
      +    }
      +
      +    // Coroutine running
      +    srun(function () use ($object, $method, $params) {
      +        $this->executeByCo($object, $method, $params);
      +    });
      +}
      +

      获取路由对应的类和方法,通过Swoft::getSingleton($className);实例化对象。

      +

      如果未开启协程,则用PhpHelper::call([$object, $method], ...$params);调用对应的方法。

      +

      开启协程的话,使用$this->executeByCo($object, $method, $params);调用对应的方法。

      +

      我们前面启动命令是php bin/swoft http:start,这里对应的类就是Swoft\Http\Server\Command\HttpServerCommand,方法就是start

      +
      /**
      +    * Start the http server
      +    *
      +    * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
      +    * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
      +    *
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    * @throws ServerException
      +    * @example
      +    *   {fullCommand}
      +    *   {fullCommand} -d
      +    *
      +    */
      +public function start(): void
      +{
      +    $server = $this->createServer();
      +
      +    // Check if it has started
      +    if ($server->isRunning()) {
      +        $masterPid = $server->getPid();
      +        output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
      +        return;
      +    }
      +
      +    // Startup settings
      +    $this->configStartOption($server);
      +
      +    $settings = $server->getSetting();
      +    // Setting
      +    $workerNum = $settings['worker_num'];
      +
      +    // Server startup parameters
      +    $mainHost = $server->getHost();
      +    $mainPort = $server->getPort();
      +    $modeName = $server->getModeName();
      +    $typeName = $server->getTypeName();
      +
      +    // Http
      +    $panel = [
      +        'HTTP' => [
      +            'listen' => $mainHost . ':' . $mainPort,
      +            'type'   => $typeName,
      +            'mode'   => $modeName,
      +            'worker' => $workerNum,
      +        ],
      +    ];
      +
      +    // Port Listeners
      +    $panel = $this->appendPortsToPanel($server, $panel);
      +
      +    Show::panel($panel);
      +
      +    output()->writeln('<success>HTTP server start success !</success>');
      +
      +    // Start the server
      +    $server->start();
      +}
      +

      这里先调用了createServer方法。

      +
      /**
      +    * @return HttpServer
      +    * @throws ReflectionException
      +    * @throws ContainerException
      +    */
      +private function createServer(): HttpServer
      +{
      +    $script  = input()->getScript();
      +    $command = $this->getFullCommand();
      +
      +    /** @var HttpServer $server */
      +    $server = bean('httpServer');
      +    $server->setScriptFile(Swoft::app()->getPath($script));
      +    $server->setFullCommand($command);
      +
      +    return $server;
      +}
      +

      获取httpServerBean实例。

      +

      框架定义在swoft-component-2.0.5\src\http-server\src\AutoLoader.php,这里声明了onRequest回调事件。

      +
      'httpServer'      => [
      +    'on' => [
      +        SwooleEvent::REQUEST => bean(RequestListener::class)
      +    ]
      +],
      +

      业务定义在swoft-2.0.5\app\bean.php

      +
      'httpServer'        => [
      +    'class'    => HttpServer::class,
      +    'port'     => 18306,
      +    'listener' => [
      +        'rpc' => bean('rpcServer')
      +    ],
      +    'process'  => [
      +//            'monitor' => bean(MonitorProcess::class)
      +//            'crontab' => bean(CrontabProcess::class)
      +    ],
      +    'on'       => [
      +//            SwooleEvent::TASK   => bean(SyncTaskListener::class),  // Enable sync task
      +        SwooleEvent::TASK   => bean(TaskListener::class),  // Enable task must task and finish event
      +        SwooleEvent::FINISH => bean(FinishListener::class)
      +    ],
      +    /* @see HttpServer::$setting */
      +    'setting'  => [
      +        'task_worker_num'       => 12,
      +        'task_enable_coroutine' => true
      +    ]
      +],
      +

      createServer返回的是一个Swoft\Http\Server\HttpServer实例。

      +

      回到HttpServerCommand类的start方法。

      +
      // Start the server
      +$server->start();
      +

      调用Swoft\Http\Server\HttpServer类的start方法。

      +
      /**
      +    * Start server
      +    *
      +    * @throws ServerException
      +    * @throws ContainerException
      +    */
      +public function start(): void
      +{
      +    $this->swooleServer = new \Swoole\Http\Server($this->host, $this->port, $this->mode, $this->type);
      +    $this->startSwoole();
      +}
      +

      声明Swoole\Http\Server对象,调用startSwoole方法。

      +

      Swoft\Http\Server\HttpServer类继承自Swoft\Server\Server类,startSwoole方法定义在这个类。

      +
      /**
      +    * Bind swoole event and start swoole server
      +    *
      +    * @throws ServerException
      +    * @throws Swoft\Bean\Exception\ContainerException
      +    */
      +protected function startSwoole(): void
      +{
      +    if (!$this->swooleServer) {
      +        throw new ServerException('You must to new server before start swoole!');
      +    }
      +
      +    // Always enable coroutine hook on server
      +    CLog::info('Swoole\Runtime::enableCoroutine');
      +    Runtime::enableCoroutine();
      +
      +    Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);
      +
      +    // Set settings
      +    $this->swooleServer->set($this->setting);
      +    // Update setting property
      +    // $this->setSetting($this->swooleServer->setting);
      +
      +    // Before Add event
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);
      +
      +    // Register events
      +    $defaultEvents = $this->defaultEvents();
      +    $swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +    // Add events
      +    $this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +
      +    //After add event
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);
      +
      +    // Before listener
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);
      +
      +    // Add port listener
      +    $this->addListener();
      +
      +    // Before bind process
      +    Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);
      +
      +    // Add Process
      +    Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);
      +
      +    // After bind process
      +    Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);
      +
      +    // Trigger event
      +    Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));
      +
      +    // Storage server instance
      +    self::$server = $this;
      +
      +    // Start swoole server
      +    $this->swooleServer->start();
      +}
      +
      $this->swooleServer->set($this->setting);
      +

      设置Swoole运行配置。

      +
      // Register events
      +$defaultEvents = $this->defaultEvents();
      +$swooleEvents  = array_merge($defaultEvents, $this->on);
      +
      +// Add events
      +$this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);
      +

      添加Swoole回调事件。

      +
      // Add port listener
      +$this->addListener();
      +

      监听端口。

      +
      // Start swoole server
      +$this->swooleServer->start();
      +

      启动Swoole\Http\Server服务。

      +

      现在服务已经启动了,那http请求是怎么被处理的呢?

      +

      这个我们下一篇再继续讲。

      +]]>
      +
      + + Swoft 框架运行分析(四) —— EventProcessor模块分析 + https://liudon.com/posts/swoft-event-processor-analysis/ + Thu, 26 Sep 2019 13:02:18 +0800 + https://liudon.com/posts/swoft-event-processor-analysis/ + <p>今天我们来看一下<code>EventProcessor</code>的实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle event register +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeEvent()) { +</span></span><span style="display:flex;"><span> CLog::warning(<span style="color:#e6db74">&#39;Stop event processor by beforeEvent return false&#39;</span>); +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/**</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> EventManager <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>eventManager <span style="color:#f92672">=</span> bean(<span style="color:#e6db74">&#39;eventManager&#39;</span>); +</span></span><span style="display:flex;"><span> [<span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2] <span style="color:#f92672">=</span> ListenerRegister::register(<span style="color:#f92672">$</span>eventManager); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Event manager initialized(</span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> listener, </span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> subscriber)&#39;</span>, <span style="color:#f92672">$</span>count1, <span style="color:#f92672">$</span>count2); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">//</span> Trigger a app init event +</span></span><span style="display:flex;"><span> Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterEvent(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>获取<code>eventManager</code>的<code>Bean</code>实例,对应为<code>Swoft\Event\Manager\EventManager</code>类。</p> + 今天我们来看一下EventProcessor的实现。

      +
      /**
      +    * Handle event register
      +    * @return bool
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeEvent()) {
      +        CLog::warning('Stop event processor by beforeEvent return false');
      +        return false;
      +    }
      +
      +    /** @var EventManager $eventManager */
      +    $eventManager = bean('eventManager');
      +    [$count1, $count2] = ListenerRegister::register($eventManager);
      +
      +    CLog::info('Event manager initialized(%d listener, %d subscriber)', $count1, $count2);
      +
      +    // Trigger a app init event
      +    Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +
      +    return $this->application->afterEvent();
      +}
      +

      获取eventManagerBean实例,对应为Swoft\Event\Manager\EventManager类。

      +
      [$count1, $count2] = ListenerRegister::register($eventManager);
      +

      调用ListenerRegister类的register方法。

      +
      /**
      +    * @param EventManager $em
      +    *
      +    * @return array
      +    */
      +public static function register(EventManager $em): array
      +{
      +    foreach (self::$listeners as $className => $eventInfo) {
      +        $listener = Swoft::getSingleton($className);
      +
      +        if (!$listener instanceof EventHandlerInterface) {
      +            throw new RuntimeException("The event listener class '{$className}' must be instanceof EventHandlerInterface");
      +        }
      +
      +        $em->addListener($listener, $eventInfo);
      +    }
      +
      +    foreach (self::$subscribers as $className) {
      +        $subscriber = Swoft::getSingleton($className);
      +        if (!$subscriber instanceof EventSubscriberInterface) {
      +            throw new RuntimeException("The event subscriber class '{$className}' must be instanceof EventSubscriberInterface");
      +        }
      +
      +        $em->addSubscriber($subscriber);
      +    }
      +
      +    $count1 = count(self::$listeners);
      +    $count2 = count(self::$subscribers);
      +    // Clear data
      +    self::$listeners = self::$subscribers = [];
      +
      +    return [$count1, $count2];
      +}
      +

      遍历ListenerRegister类下的$listeners$subscribers属性,绑定事件到eventManagerBean实例上。

      +

      这里的$listeners$subscribers是从哪里来的呢?

      +

      这里以http-server为例。

      +

      swoft-component-2.0.5\src\http-server\src\Listener目录下,存在下面三个文件。

      +
      AfterRequestListener.php
      +AppInitCompleteListener.php
      +BeforeRequestListener.php
      +

      这里我们以AppInitCompleteListener.php为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      可以看到这里通过@Listener(SwoftEvent::APP_INIT_COMPLETE),使用了Swoft\Event\Annotation\Mapping\Listener类注解,对应的注解解析类为Swoft\Event\Annotation\Parser\ListenerParser

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Event\Annotation\Parser;
      +
      +use Doctrine\Common\Annotations\AnnotationException;
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\ListenerRegister;
      +
      +/**
      + * Class ListenerParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(Listener::class)
      + */
      +class ListenerParser extends Parser
      +{
      +    /**
      +     * @param int      $type
      +     * @param Listener $annotation
      +     *
      +     * @return array
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $annotation): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@Listener` must be defined on class!');
      +        }
      +
      +        // collect listeners
      +        ListenerRegister::addListener($this->className, [
      +            // event name => listener priority
      +            $annotation->getEvent() => $annotation->getPriority()
      +        ]);
      +
      +        return [$this->className, $this->className, Bean::SINGLETON, ''];
      +    }
      +}
      +
      /**
      +    * @param string $className
      +    * @param array  $definition [event name => listener priority]
      +    */
      +public static function addListener(string $className, array $definition = []): void
      +{
      +    // Collect listeners
      +    self::$listeners[$className] = $definition;
      +}
      +

      可以看到这里通过ListenerRegister::addListener方法,往ListenerRegister上注册了$listeners属性。

      +

      属性$listeners$subscribers的值,都是通过注解解析得来。

      +

      这里我们回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      trigger的方法定义如下。

      +
      /**
      +    * Trigger an swoft application event
      +    *
      +    * @param string|EventInterface $event eg: 'app.start' 'app.stop'
      +    * @param null|mixed            $target
      +    * @param array                 $params
      +    *
      +    * @return EventInterface
      +    */
      +public static function trigger($event, $target = null, ...$params): EventInterface
      +{
      +    /** @see EventManager::trigger() */
      +    return BeanFactory::getSingleton('eventManager')->trigger($event, $target, $params);
      +}
      +

      这里调用了eventManager这个Bean实例的trigger方法。

      +
      /**
      +    * Trigger an event. Can accept an EventInterface or will create one if not passed
      +    *
      +    * @param string|EventInterface $event  'app.start' 'app.stop'
      +    * @param mixed|string          $target It is object or string.
      +    * @param array|mixed           $args
      +    *
      +    * @return EventInterface
      +    * @throws InvalidArgumentException
      +    */
      +public function trigger($event, $target = null, array $args = []): EventInterface
      +{
      +    if ($isString = is_string($event)) {
      +        $name = trim($event);
      +    } elseif ($event instanceof EventInterface) {
      +        $name = trim($event->getName());
      +    } else {
      +        throw new InvalidArgumentException('Invalid event params for trigger event handler');
      +    }
      +
      +    $shouldCall = [];
      +
      +    // Have matched listener
      +    if (isset($this->listenedEvents[$name])) {
      +        $shouldCall[$name] = '';
      +    }
      +
      +    // Like 'app.db.query' => prefix: 'app.db'
      +    if ($pos = strrpos($name, '.')) {
      +        $prefix = substr($name, 0, $pos);
      +
      +        // Have a wildcards listener. eg 'app.db.*'
      +        $wildcardEvent = $prefix . '.*';
      +        if (isset($this->listenedEvents[$wildcardEvent])) {
      +            $shouldCall[$wildcardEvent] = substr($name, $pos + 1);
      +        }
      +    }
      +
      +    // Not found listeners
      +    if (!$shouldCall) {
      +        return $isString ? $this->basicEvent : $event;
      +    }
      +
      +    /** @var EventInterface $event */
      +    if ($isString) {
      +        $event = $this->events[$name] ?? $this->basicEvent;
      +    }
      +
      +    // Initial value
      +    $event->setName($name);
      +    $event->setParams($args);
      +    $event->setTarget($target);
      +    $event->stopPropagation(false);
      +
      +    // Notify event listeners
      +    foreach ($shouldCall as $name => $method) {
      +        $this->triggerListeners($this->listeners[$name], $event, $method);
      +
      +        if ($event->isPropagationStopped()) {
      +            return $event;
      +        }
      +    }
      +
      +    // Have global wildcards '*' listener.
      +    if (isset($this->listenedEvents['*'])) {
      +        $this->triggerListeners($this->listeners['*'], $event);
      +    }
      +
      +    return $event;
      +}
      +

      如果存在对应的事件,调用triggerListeners方法。

      +
      /**
      +    * @param array|ListenerQueue $listeners
      +    * @param EventInterface      $event
      +    * @param string              $method
      +    */
      +protected function triggerListeners($listeners, EventInterface $event, string $method = ''): void
      +{
      +    // $handled = false;
      +    $name     = $event->getName();
      +    $callable = false === strpos($name, '.');
      +
      +    // 循环调用监听器,处理事件
      +    foreach ($listeners as $listener) {
      +        if ($event->isPropagationStopped()) {
      +            break;
      +        }
      +
      +        if (is_object($listener)) {
      +            if ($listener instanceof EventHandlerInterface) {
      +                $listener->handle($event);
      +            } elseif ($method && method_exists($listener, $method)) {
      +                $listener->$method($event);
      +            } elseif ($callable && method_exists($listener, $name)) {
      +                $listener->$name($event);
      +            } elseif (method_exists($listener, '__invoke')) {
      +                $listener($event);
      +            }
      +        } elseif (is_callable($listener)) {
      +            $listener($event);
      +        }
      +    }
      +}
      +

      遍历事件回调,执行对应方法。

      +

      回到EventProcessor类的handle方法。

      +
      // Trigger a app init event
      +Swoft::trigger(SwoftEvent::APP_INIT_COMPLETE);
      +

      这里的事件为SwoftEvent::APP_INIT_COMPLETE,所以这里会执行这个事件下的所有回调。

      +

      这里以Swoft\Http\Server\Listener\AppInitCompleteListener为例。

      +
      <?php
      +
      +namespace Swoft\Http\Server\Listener;
      +
      +use function bean;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Event\Annotation\Mapping\Listener;
      +use Swoft\Event\EventHandlerInterface;
      +use Swoft\Event\EventInterface;
      +use Swoft\Http\Server\Exception\HttpServerException;
      +use Swoft\Http\Server\Middleware\MiddlewareRegister;
      +use Swoft\Http\Server\Router\Router;
      +use Swoft\Http\Server\Router\RouteRegister;
      +use Swoft\SwoftEvent;
      +
      +/**
      + * Class AppInitCompleteListener
      + * @since 2.0
      + *
      + * @Listener(SwoftEvent::APP_INIT_COMPLETE)
      + */
      +class AppInitCompleteListener implements EventHandlerInterface
      +{
      +    /**
      +     * @param EventInterface $event
      +     *
      +     * @throws ContainerException
      +     * @throws ReflectionException
      +     * @throws HttpServerException
      +     */
      +    public function handle(EventInterface $event): void
      +    {
      +        /** @var Router $router Register HTTP routes */
      +        $router = bean('httpRouter');
      +
      +        RouteRegister::registerRoutes($router);
      +
      +        // Register middleware
      +        MiddlewareRegister::register();
      +    }
      +}
      +

      这里使用了Swoft\Event\Annotation\Mapping\Listener注解,对应的事件为SwoftEvent::APP_INIT_COMPLETE

      +

      按照上面的分析,这里会调用到AppInitCompleteListenerhandle方法,获取httpRouterBean实例,注册http服务的路由信息和中间件。

      +

      到这里,我们大概清楚了EventProcessor这个模块的作用,注册了所有事件的回调。

      +]]>
      +
      + + Swoft 框架运行分析(三) —— BeanProcessor模块分析 + https://liudon.com/posts/swoft-bean-processor-analysis/ + Mon, 02 Sep 2019 18:29:06 +0800 + https://liudon.com/posts/swoft-bean-processor-analysis/ + <p>今天讲一下<code>BeanProcessor</code>模块,先看一下<code>handle</code>方法实现。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">/**</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> Handle bean +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws ReflectionException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span> <span style="color:#960050;background-color:#1e0010">@</span>throws AnnotationException +</span></span><span style="display:flex;"><span> <span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span>public function handle(): <span style="color:#a6e22e">bool</span> +</span></span><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>beforeBean()) { +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> false; +</span></span><span style="display:flex;"><span> } +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>handler <span style="color:#f92672">=</span> new BeanHandler(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>definitions <span style="color:#f92672">=</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>getDefinitions(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>parsers <span style="color:#f92672">=</span> AnnotationRegister::getParsers(); +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>annotations <span style="color:#f92672">=</span> AnnotationRegister::getAnnotations(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> BeanFactory::addDefinitions(<span style="color:#f92672">$</span>definitions); +</span></span><span style="display:flex;"><span> BeanFactory::addAnnotations(<span style="color:#f92672">$</span>annotations); +</span></span><span style="display:flex;"><span> BeanFactory::addParsers(<span style="color:#f92672">$</span>parsers); +</span></span><span style="display:flex;"><span> BeanFactory::setHandler(<span style="color:#f92672">$</span>handler); +</span></span><span style="display:flex;"><span> BeanFactory::init(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">/*</span> <span style="color:#960050;background-color:#1e0010">@</span><span style="color:#66d9ef">var</span> Config <span style="color:#f92672">$</span>config<span style="color:#f92672">*/</span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>config <span style="color:#f92672">=</span> BeanFactory::getBean(<span style="color:#e6db74">&#39;config&#39;</span>); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config path=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getPath()); +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;config env=</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">&#39;</span>, <span style="color:#f92672">$</span>config<span style="color:#f92672">-&gt;</span>getEnv()); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#f92672">$</span>stats <span style="color:#f92672">=</span> BeanFactory::getStats(); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> CLog::info(<span style="color:#e6db74">&#39;Bean is initialized(</span><span style="color:#e6db74">%s</span><span style="color:#e6db74">)&#39;</span>, SwoftHelper::formatStats(<span style="color:#f92672">$</span>stats)); +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#f92672">$</span>this<span style="color:#f92672">-&gt;</span>application<span style="color:#f92672">-&gt;</span>afterBean(); +</span></span><span style="display:flex;"><span>} +</span></span></code></pre></div><p>先通过<code>getDefinitions</code>方法获取所有的Bean定义。</p> + 今天讲一下BeanProcessor模块,先看一下handle方法实现。

      +
      /**
      +    * Handle bean
      +    *
      +    * @return bool
      +    * @throws ReflectionException
      +    * @throws AnnotationException
      +    */
      +public function handle(): bool
      +{
      +    if (!$this->application->beforeBean()) {
      +        return false;
      +    }
      +
      +    $handler     = new BeanHandler();
      +    $definitions = $this->getDefinitions();
      +    $parsers     = AnnotationRegister::getParsers();
      +    $annotations = AnnotationRegister::getAnnotations();
      +
      +    BeanFactory::addDefinitions($definitions);
      +    BeanFactory::addAnnotations($annotations);
      +    BeanFactory::addParsers($parsers);
      +    BeanFactory::setHandler($handler);
      +    BeanFactory::init();
      +
      +    /* @var Config $config*/
      +    $config = BeanFactory::getBean('config');
      +
      +    CLog::info('config path=%s', $config->getPath());
      +    CLog::info('config env=%s', $config->getEnv());
      +
      +    $stats = BeanFactory::getStats();
      +
      +    CLog::info('Bean is initialized(%s)', SwoftHelper::formatStats($stats));
      +
      +    return $this->application->afterBean();
      +}
      +

      先通过getDefinitions方法获取所有的Bean定义。

      +
      /**
      +    * Get bean definitions
      +    *
      +    * @return array
      +    */
      +private function getDefinitions(): array
      +{
      +    // Core beans
      +    $definitions = [];
      +    $autoLoaders = AnnotationRegister::getAutoLoaders();
      +
      +    // get disabled loaders by application
      +    $disabledLoaders = $this->application->getDisabledAutoLoaders();
      +
      +    foreach ($autoLoaders as $autoLoader) {
      +        if (!$autoLoader instanceof DefinitionInterface) {
      +            continue;
      +        }
      +
      +        $loaderClass = get_class($autoLoader);
      +
      +        // If the component is disabled by user.
      +        if (isset($disabledLoaders[$loaderClass])) {
      +            CLog::info('Auto loader(%s) is <cyan>disabled</cyan>, skip handle it', $loaderClass);
      +            continue;
      +        }
      +
      +        // If the component is not enabled.
      +        if ($autoLoader instanceof ComponentInterface && !$autoLoader->isEnable()) {
      +            continue;
      +        }
      +
      +        $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());
      +    }
      +
      +    // Bean definitions
      +    $beanFile = $this->application->getBeanFile();
      +    $beanFile = alias($beanFile);
      +
      +    if (!file_exists($beanFile)) {
      +        throw new InvalidArgumentException(
      +            sprintf('The bean config file of %s is not exist!', $beanFile)
      +        );
      +    }
      +
      +    $beanDefinitions = require $beanFile;
      +    $definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +
      +    return $definitions;
      +}
      +

      通过AnnotationRegister::getAutoLoaders()拿到所有的autoloader对象,排除掉非DefinitionInterface对象,通过bean()方法获取定义的Bean信息。

      +

      这里以http-server\src\AutoLoader.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Http\Server;
      +
      +use function bean;
      +use function dirname;
      +use ReflectionException;
      +use Swoft\Bean\Exception\ContainerException;
      +use Swoft\Helper\ComposerJSON;
      +use Swoft\Http\Message\ContentType;
      +use Swoft\Http\Message\Response;
      +use Swoft\Http\Server\Formatter\HtmlResponseFormatter;
      +use Swoft\Http\Server\Formatter\JsonResponseFormatter;
      +use Swoft\Http\Server\Formatter\XmlResponseFormatter;
      +use Swoft\Http\Server\Parser\JsonRequestParser;
      +use Swoft\Http\Server\Parser\XmlRequestParser;
      +use Swoft\Http\Server\Swoole\RequestListener;
      +use Swoft\Server\SwooleEvent;
      +use Swoft\SwoftComponent;
      +
      +/**
      + * Class AutoLoader
      + *
      + * @since 2.0
      + */
      +class AutoLoader extends SwoftComponent
      +{
      +    /**
      +     * Metadata information for the component.
      +     *
      +     * @return array
      +     * @see ComponentInterface::getMetadata()
      +     */
      +    public function metadata(): array
      +    {
      +        $jsonFile = dirname(__DIR__) . '/composer.json';
      +
      +        return ComposerJSON::open($jsonFile)->getMetadata();
      +    }
      +
      +    /**
      +     * Get namespace and dirs
      +     *
      +     * @return array
      +     */
      +    public function getPrefixDirs(): array
      +    {
      +        return [
      +            __NAMESPACE__ => __DIR__,
      +        ];
      +    }
      +
      +    /**
      +     * @return array
      +     * @throws ReflectionException
      +     * @throws ContainerException
      +     */
      +    public function beans(): array
      +    {
      +        return [
      +            'httpRequest'     => [
      +                'parsers' => [
      +                    ContentType::XML  => bean(XmlRequestParser::class),
      +                    ContentType::JSON => bean(JsonRequestParser::class),
      +                ]
      +            ],
      +            'httpResponse'    => [
      +                'format'     => Response::FORMAT_JSON,
      +                'formatters' => [
      +                    Response::FORMAT_HTML => bean(HtmlResponseFormatter::class),
      +                    Response::FORMAT_JSON => bean(JsonResponseFormatter::class),
      +                    Response::FORMAT_XML  => bean(XmlResponseFormatter::class),
      +                ]
      +            ],
      +            'acceptFormatter' => [
      +                'formats' => [
      +                    ContentType::JSON => Response::FORMAT_JSON,
      +                    ContentType::HTML => Response::FORMAT_HTML,
      +                    ContentType::XML  => Response::FORMAT_XML,
      +                ]
      +            ],
      +            'httpServer'      => [
      +                'on' => [
      +                    SwooleEvent::REQUEST => bean(RequestListener::class)
      +                ]
      +            ],
      +            'httpRouter'      => [
      +                'name'            => 'swoft-http-router',
      +                // config
      +                'ignoreLastSlash' => true,
      +                'tmpCacheNumber'  => 500,
      +            ],
      +        ];
      +    }
      +}
      +

      可以看到,这里通过beans()定义了httpRequesthttpResponseacceptFormatterhttpServerhttpRouter四个Bean对象。

      +

      回到上面getDefinitions方法。

      +

      $definitions = ArrayHelper::merge($definitions, $autoLoader->beans());

      +

      然后将Bean信息添加到definitions对象上。

      +

      之后通过$beanFile = $this->application->getBeanFile();获取bean配置文件。

      +
      $beanDefinitions = require $beanFile;
      +$definitions     = ArrayHelper::merge($definitions, $beanDefinitions);
      +

      加载配置文件,然后将Bean信息添加到definitions对象上。

      +

      可以看到Bean有两种定义方式:通过AutoLoader和配置文件,与swoft官方文档里的说明一致。

      +

      回到handle方法。

      +
      $parsers     = AnnotationRegister::getParsers();
      +$annotations = AnnotationRegister::getAnnotations();
      +

      还记得上一篇文章最后提到的AnnotationRegister类的annotationsparsers两个属性吗?这里通过getParsersgetAnnotations获取这两个属性。

      +
      BeanFactory::addDefinitions($definitions);
      +BeanFactory::addAnnotations($annotations);
      +BeanFactory::addParsers($parsers);
      +BeanFactory::setHandler($handler);
      +BeanFactory::init();
      +

      向BeanFatory注册信息。

      +
      /**
      +    * Init
      +    *
      +    * @return void
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function init(): void
      +{
      +    Container::getInstance()->init();
      +}
      +
      +...
      +
      +/**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public static function addDefinitions(array $definitions): void
      +{
      +    Container::getInstance()->addDefinitions($definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public static function addAnnotations(array $annotations): void
      +{
      +    Container::getInstance()->addAnnotations($annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public static function addParsers(array $annotationParsers): void
      +{
      +    Container::getInstance()->addParsers($annotationParsers);
      +}
      +
      +/**
      +    * Set bean handler
      +    *
      +    * @param HandlerInterface $handler
      +    */
      +public static function setHandler(HandlerInterface $handler): void
      +{
      +    Container::getInstance()->setHandler($handler);
      +}
      +

      这里可以看到所有的方法,最终都调用的是Swoft\Bean\Container类。

      +
      /**
      +    * Add definitions
      +    *
      +    * @param array $definitions
      +    *
      +    * @return void
      +    */
      +public function addDefinitions(array $definitions): void
      +{
      +    $this->definitions = ArrayHelper::merge($this->definitions, $definitions);
      +}
      +
      +/**
      +    * Add annotations
      +    *
      +    * @param array $annotations
      +    *
      +    * @return void
      +    */
      +public function addAnnotations(array $annotations): void
      +{
      +    $this->annotations = ArrayHelper::merge($this->annotations, $annotations);
      +}
      +
      +/**
      +    * Add annotation parsers
      +    *
      +    * @param array $annotationParsers
      +    *
      +    * @return void
      +    */
      +public function addParsers(array $annotationParsers): void
      +{
      +    $this->parsers = ArrayHelper::merge($this->parsers, $annotationParsers);
      +}
      +
      +
      +/**
      +    * @param HandlerInterface $handler
      +    */
      +public function setHandler(HandlerInterface $handler): void
      +{
      +    $this->handler = $handler;
      +}
      +

      这四个方法就是注册属性,接下来是重头戏init方法。

      +
      /**
      +    * Init
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function init(): void
      +{
      +    // Parse annotations
      +    $this->parseAnnotations();
      +
      +    // Parse definitions
      +    $this->parseDefinitions();
      +
      +    // Init beans
      +    $this->initializeBeans();
      +}
      +

      先看parseAnnotations方法,从代码注释上也可以看出大概,解析注解,接下来我们看下具体是如何实现的。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseAnnotations(): void
      +{
      +    $annotationParser = new AnnotationObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +    $annotationData   = $annotationParser->parseAnnotations($this->annotations, $this->parsers);
      +
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $annotationData;
      +}
      +

      声明了一个AnnotationObjParser对象,调用了parseAnnotations方法。

      +
      /**
      +    * Parse annotations
      +    *
      +    * @param array $annotations
      +    * @param array $parsers
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    */
      +public function parseAnnotations(array $annotations, array $parsers): array
      +{
      +    $this->parsers     = $parsers;
      +    $this->annotations = $annotations;
      +
      +    foreach ($this->annotations as $loadNameSpace => $classes) {
      +        foreach ($classes as $className => $classOneAnnotations) {
      +            $this->parseOneClassAnnotations($className, $classOneAnnotations);
      +        }
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      这里遍历所有的annotation类,循环调用parseOneClassAnnotations进行解析。

      +
      /**
      +    * Parse class all annotations
      +    *
      +    * @param string $className
      +    * @param array  $classOneAnnotations
      +    *
      +    * @throws AnnotationException
      +    */
      +private function parseOneClassAnnotations(string $className, array $classOneAnnotations): void
      +{
      +    // Check class annotation tag
      +    if (!isset($classOneAnnotations['annotation'])) {
      +        throw new AnnotationException(
      +            sprintf('Property or method(%s) with `@xxx` must be define class annotation', $className)
      +        );
      +    }
      +
      +    // Parse class annotations
      +    $classAnnotations = $classOneAnnotations['annotation'];
      +    $reflectionClass  = $classOneAnnotations['reflection'];
      +
      +    $classAry = [
      +        $className,
      +        $reflectionClass,
      +        $classAnnotations
      +    ];
      +
      +    $objectDefinition = $this->parseClassAnnotations($classAry);
      +
      +    // Parse property annotations
      +    $propertyInjects        = [];
      +    $propertyAllAnnotations = $classOneAnnotations['properties'] ?? [];
      +    foreach ($propertyAllAnnotations as $propertyName => $propertyOneAnnotations) {
      +        $proAnnotations = $propertyOneAnnotations['annotation'] ?? [];
      +        $propertyInject = $this->parsePropertyAnnotations($classAry, $propertyName, $proAnnotations);
      +        if ($propertyInject) {
      +            $propertyInjects[$propertyName] = $propertyInject;
      +        }
      +    }
      +
      +    // Parse method annotations
      +    $methodInjects        = [];
      +    $methodAllAnnotations = $classOneAnnotations['methods'] ?? [];
      +    foreach ($methodAllAnnotations as $methodName => $methodOneAnnotations) {
      +        $methodAnnotations = $methodOneAnnotations['annotation'] ?? [];
      +
      +        $methodInject = $this->parseMethodAnnotations($classAry, $methodName, $methodAnnotations);
      +        if ($methodInject) {
      +            $methodInjects[$methodName] = $methodInject;
      +        }
      +    }
      +
      +    if (!$objectDefinition) {
      +        return;
      +    }
      +
      +    if (!empty($propertyInjects)) {
      +        $objectDefinition->setPropertyInjections($propertyInjects);
      +    }
      +
      +    if (!empty($methodInjects)) {
      +        $objectDefinition->setMethodInjections($methodInjects);
      +    }
      +
      +    // Object definition and class name
      +    $name         = $objectDefinition->getName();
      +    $aliase       = $objectDefinition->getAlias();
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $name;
      +
      +    $this->classNames[$className]   = array_unique($classNames);
      +    $this->objectDefinitions[$name] = $objectDefinition;
      +
      +    if (!empty($aliase)) {
      +        $this->aliases[$aliase] = $name;
      +    }
      +}
      +

      这里可以看到分别有类注解、属性注解和方法注解三类。

      +

      对应官方文档的注解说明

      +
      /**
      +    * @param array $classAry
      +    *
      +    * @return ObjectDefinition|null
      +    */
      +private function parseClassAnnotations(array $classAry): ?ObjectDefinition
      +{
      +    [, , $classAnnotations] = $classAry;
      +
      +    $objectDefinition = null;
      +    foreach ($classAnnotations as $annotation) {
      +        $annotationClass = get_class($annotation);
      +        if (!isset($this->parsers[$annotationClass])) {
      +            continue;
      +        }
      +
      +        $parserClassName  = $this->parsers[$annotationClass];
      +        $annotationParser = $this->getAnnotationParser($classAry, $parserClassName);
      +
      +        $data = $annotationParser->parse(Parser::TYPE_CLASS, $annotation);
      +        if (empty($data)) {
      +            continue;
      +        }
      +
      +        if (count($data) !== 4) {
      +            throw new InvalidArgumentException(sprintf('%s annotation parse must be 4 size', $annotationClass));
      +        }
      +
      +        [$name, $className, $scope, $alias] = $data;
      +        $name = empty($name) ? $className : $name;
      +
      +        if (empty($className)) {
      +            throw new InvalidArgumentException(sprintf('%s with class name can not be empty', $annotationClass));
      +        }
      +
      +        // Multiple coverage
      +        $objectDefinition = new ObjectDefinition($name, $className, $scope, $alias);
      +    }
      +
      +    return $objectDefinition;
      +}
      +

      类注解,这里会调用对应解析类的parse方法。

      +

      这里以websocket-server\src\Annotation\Mapping\WsModule.phpwebsocket-server\src\Annotation\Parser\WsModuleParser.php为例。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Mapping;
      +
      +use Doctrine\Common\Annotations\Annotation\Attribute;
      +use Doctrine\Common\Annotations\Annotation\Attributes;
      +use Doctrine\Common\Annotations\Annotation\Required;
      +use Doctrine\Common\Annotations\Annotation\Target;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +
      +/**
      + * Class WebSocket - mark an websocket module handler class
      + *
      + * @since 2.0
      + *
      + * @Annotation
      + * @Target("CLASS")
      + * @Attributes(
      + *     @Attribute("name", type="string"),
      + *     @Attribute("path", type="string"),
      + *     @Attribute("controllers", type="array"),
      + *     @Attribute("messageParser", type="string"),
      + * )
      + */
      +final class WsModule
      +{
      +    /**
      +     * Websocket route path.(it must unique in a application)
      +     *
      +     * @var string
      +     * @Required()
      +     */
      +    private $path = '/';
      +
      +    /**
      +     * Module name.
      +     *
      +     * @var string
      +     */
      +    private $name = '';
      +
      +    /**
      +     * Routing path params binding. eg. {"id"="\d+"}
      +     *
      +     * @var array
      +     */
      +    private $params = [];
      +
      +    /**
      +     * Message controllers of the module
      +     *
      +     * @var string[]
      +     */
      +    private $controllers = [];
      +
      +    /**
      +     * Message parser class for the module
      +     *
      +     * @var string
      +     */
      +    private $messageParser = RawTextParser::class;
      +
      +    /**
      +     * Default message command. Format 'controller.action'
      +     *
      +     * @var string
      +     */
      +    private $defaultCommand = 'home.index';
      +
      +    /**
      +     * Default message opcode for response. please see WEBSOCKET_OPCODE_*
      +     *
      +     * @var int
      +     */
      +    private $defaultOpcode = 0;
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $values
      +     */
      +    public function __construct(array $values)
      +    {
      +        if (isset($values['value'])) {
      +            $this->path = (string)$values['value'];
      +        } elseif (isset($values['path'])) {
      +            $this->path = (string)$values['path'];
      +        }
      +
      +        if (isset($values['name'])) {
      +            $this->name = (string)$values['name'];
      +        }
      +
      +        if (isset($values['params'])) {
      +            $this->params = (array)$values['params'];
      +        }
      +
      +        if (isset($values['controllers'])) {
      +            $this->controllers = (array)$values['controllers'];
      +        }
      +
      +        if (isset($values['messageParser'])) {
      +            $this->messageParser = $values['messageParser'];
      +        }
      +
      +        if (isset($values['defaultOpcode'])) {
      +            $this->defaultOpcode = (int)$values['defaultOpcode'];
      +        }
      +
      +        if (isset($values['defaultCommand'])) {
      +            $this->defaultCommand = $values['defaultCommand'];
      +        }
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getPath(): string
      +    {
      +        return $this->path;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getMessageParser(): string
      +    {
      +        return $this->messageParser;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getDefaultCommand(): string
      +    {
      +        return $this->defaultCommand;
      +    }
      +
      +    /**
      +     * @return string
      +     */
      +    public function getName(): string
      +    {
      +        return $this->name;
      +    }
      +
      +    /**
      +     * @return string[]
      +     */
      +    public function getControllers(): array
      +    {
      +        return $this->controllers;
      +    }
      +
      +    /**
      +     * @return array
      +     */
      +    public function getParams(): array
      +    {
      +        return $this->params;
      +    }
      +
      +    /**
      +     * @return int
      +     */
      +    public function getDefaultOpcode(): int
      +    {
      +        return $this->defaultOpcode;
      +    }
      +}
      +

      WsModule声明了一个类注解。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\WebSocket\Server\Annotation\Parser;
      +
      +use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
      +use Swoft\Annotation\Annotation\Parser\Parser;
      +use Swoft\Annotation\Exception\AnnotationException;
      +use Swoft\Bean\Annotation\Mapping\Bean;
      +use Swoft\Stdlib\Helper\Str;
      +use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
      +use Swoft\WebSocket\Server\MessageParser\RawTextParser;
      +use Swoft\WebSocket\Server\Router\RouteRegister;
      +
      +/**
      + * Class WebSocketParser
      + *
      + * @since 2.0
      + *
      + * @AnnotationParser(WsModule::class)
      + */
      +class WsModuleParser extends Parser
      +{
      +    /**
      +     * Parse object
      +     *
      +     * @param int      $type Class or Method or Property
      +     * @param WsModule $ann  Annotation object
      +     *
      +     * @return array
      +     * Return empty array is nothing to do!
      +     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
      +     * When property type return [$propertyValue, $isRef] is to reference value
      +     * @throws AnnotationException
      +     */
      +    public function parse(int $type, $ann): array
      +    {
      +        if ($type !== self::TYPE_CLASS) {
      +            throw new AnnotationException('`@WsModule` must be defined on class!');
      +        }
      +
      +        $class = $this->className;
      +
      +        RouteRegister::bindModule($class, [
      +            'path'           => $ann->getPath() ?: Str::getClassName($class, 'Module'),
      +            'name'           => $ann->getName(),
      +            'params'         => $ann->getParams(),
      +            'class'          => $class,
      +            'eventMethods'   => [],
      +            'controllers'    => $ann->getControllers(),
      +            'messageParser'  => $ann->getMessageParser() ?: RawTextParser::class,
      +            'defaultOpcode' => $ann->getDefaultOpcode(),
      +            'defaultCommand' => $ann->getDefaultCommand(),
      +        ]);
      +
      +        return [$class, $class, Bean::SINGLETON, ''];
      +    }
      +}
      +

      按上一篇文章说明,这里WsModuleParser会被标记为注解类WsModule的注解解析类。

      +

      解析注解的时候,会调用WsModuleParserparse方法,这里通过RouteRegister::bindModule做了一些路由操作,这里后续再讲,这里不做深入介绍。

      +

      属性和方法注解,也是类似的,parseAnnotations方法就讲完了。

      +

      回到Container类的init方法,接下来调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    */
      +private function parseDefinitions(): void
      +{
      +    $annotationParser = new DefinitionObjParser(
      +        $this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases
      +    );
      +
      +    // Collect info
      +    $definitionData = $annotationParser->parseDefinitions();
      +    [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases] = $definitionData;
      +}
      +

      声明了一个DefinitionObjParser对象,调用了parseDefinitions方法。

      +
      /**
      +    * Parse definitions
      +    *
      +    * @return array
      +    */
      +public function parseDefinitions(): array
      +{
      +    foreach ($this->definitions as $beanName => $definition) {
      +        if (isset($this->objectDefinitions[$beanName])) {
      +            $objectDefinition = $this->objectDefinitions[$beanName];
      +            $this->resetObjectDefinition($beanName, $objectDefinition, $definition);
      +            continue;
      +        }
      +
      +        $this->createObjectDefinition($beanName, $definition);
      +    }
      +
      +    return [$this->definitions, $this->objectDefinitions, $this->classNames, $this->aliases];
      +}
      +

      遍历所有的Bean对象,调用createObjectDefinition方法。

      +
      /**
      +    * Create object definition for definition
      +    *
      +    * @param string $beanName
      +    * @param array  $definition
      +    */
      +private function createObjectDefinition(string $beanName, array $definition): void
      +{
      +    $className = $definition['class'] ?? '';
      +    if (empty($className)) {
      +        throw new InvalidArgumentException(sprintf('%s key for definition must be defined class', $beanName));
      +    }
      +
      +    $objDefinition = new ObjectDefinition($beanName, $className);
      +    $objDefinition = $this->updateObjectDefinitionByDefinition($objDefinition, $definition);
      +
      +    $classNames   = $this->classNames[$className] ?? [];
      +    $classNames[] = $beanName;
      +
      +    $this->classNames[$className]       = array_unique($classNames);
      +    $this->objectDefinitions[$beanName] = $objDefinition;
      +}
      +

      声明了ObjectDefinition对象,调用了updateObjectDefinitionByDefinition方法。

      +
      /**
      +    * Update definition
      +    *
      +    * @param ObjectDefinition $objDfn
      +    * @param array            $definition
      +    *
      +    * @return ObjectDefinition
      +    */
      +private function updateObjectDefinitionByDefinition(ObjectDefinition $objDfn, array $definition): ObjectDefinition
      +{
      +    [$constructInject, $propertyInjects, $option] = $this->parseDefinition($definition);
      +
      +    // Set construct inject
      +    if (!empty($constructInject)) {
      +        $objDfn->setConstructorInjection($constructInject);
      +    }
      +
      +    // Set property inject
      +    foreach ($propertyInjects as $propertyName => $propertyInject) {
      +        $objDfn->setPropertyInjection($propertyName, $propertyInject);
      +    }
      +
      +    $scopes = [
      +        Bean::SINGLETON,
      +        Bean::PROTOTYPE,
      +        Bean::REQUEST,
      +    ];
      +
      +    $scope = $option['scope'] ?? '';
      +    $alias = $option['alias'] ?? '';
      +
      +    if (!empty($scope) && !in_array($scope, $scopes, true)) {
      +        throw new InvalidArgumentException('Scope for definition is not undefined');
      +    }
      +
      +    // Update scope
      +    if (!empty($scope)) {
      +        $objDfn->setScope($scope);
      +    }
      +
      +    // Update alias
      +    if (!empty($alias)) {
      +        $objDfn->setAlias($alias);
      +
      +        $objAlias = $objDfn->getAlias();
      +        unset($this->aliases[$objAlias]);
      +
      +        $this->aliases[$alias] = $objDfn->getName();
      +    }
      +
      +    return $objDfn;
      +}
      +

      这里调用了parseDefinition方法进行解析。

      +
      /**
      +    * Parse definition
      +    *
      +    * @param array $definition
      +    *
      +    * @return array
      +    */
      +private function parseDefinition(array $definition): array
      +{
      +    // Remove class key
      +    unset($definition['class']);
      +
      +    // Parse construct
      +    $constructArgs = $definition[0] ?? [];
      +    if (!is_array($constructArgs)) {
      +        throw new InvalidArgumentException('Construct args for definition must be array');
      +    }
      +
      +    // Parse construct args
      +    $argInjects = [];
      +    foreach ($constructArgs as $arg) {
      +        [$argValue, $argIsRef] = $this->getValueByRef($arg);
      +
      +        $argInjects[] = new ArgsInjection($argValue, $argIsRef);
      +    }
      +
      +    // Set construct inject
      +    $constructInject = null;
      +    if (!empty($argInjects)) {
      +        $constructInject = new MethodInjection('__construct', $argInjects);
      +    }
      +
      +    // Remove construct definition
      +    unset($definition[0]);
      +
      +    // Parse definition option
      +    $option = $definition['__option'] ?? [];
      +    if (!is_array($option)) {
      +        throw new InvalidArgumentException('__option for definition must be array');
      +    }
      +
      +    // Remove `__option`
      +    unset($definition['__option']);
      +
      +    // Parse definition properties
      +    $propertyInjects = [];
      +    foreach ($definition as $propertyName => $propertyValue) {
      +        if (!is_string($propertyName)) {
      +            throw new InvalidArgumentException('Property key from definition must be string');
      +        }
      +
      +        [$proValue, $proIsRef] = $this->getValueByRef($propertyValue);
      +
      +        // Parse property for array
      +        if (is_array($proValue)) {
      +            $proValue = $this->parseArrayProperty($proValue);
      +        }
      +
      +        $propertyInject = new PropertyInjection($propertyName, $proValue, $proIsRef);
      +
      +        $propertyInjects[$propertyName] = $propertyInject;
      +    }
      +
      +    return [$constructInject, $propertyInjects, $option];
      +}
      +

      解析__construct方法和传参,解析属性信息。

      +

      回到updateObjectDefinitionByDefinition方法,将__construct和类属性信息注册到ObjectDefinition对象上,到这里parseDefinitions方法执行完毕。

      +

      回到Container类的init方法,接下来调用了initializeBeans方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @throws InvalidArgumentException
      +    * @throws ReflectionException
      +    */
      +private function initializeBeans(): void
      +{
      +    /* @var ObjectDefinition $objectDefinition */
      +    foreach ($this->objectDefinitions as $beanName => $objectDefinition) {
      +        $scope = $objectDefinition->getScope();
      +        // Exclude request
      +        if ($scope === Bean::REQUEST) {
      +            $this->requestDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // Exclude session
      +        if ($scope === Bean::SESSION) {
      +            $this->sessionDefinitions[$beanName] = $objectDefinition;
      +            unset($this->objectDefinitions[$beanName]);
      +            continue;
      +        }
      +
      +        // New bean
      +        $this->newBean($beanName);
      +    }
      +}
      +

      对于scope不为Bean::REQUESTBean::SESSION的,调用newBean方法。

      +
      /**
      +    * Initialize beans
      +    *
      +    * @param string $beanName
      +    * @param string $id
      +    *
      +    * @return object
      +    * @throws ReflectionException
      +    */
      +private function newBean(string $beanName, string $id = '')
      +{
      +    // First, check bean whether has been create.
      +    if (isset($this->singletonPool[$beanName]) || isset($this->prototypePool[$beanName])) {
      +        return $this->get($beanName);
      +    }
      +
      +    // Get object definition
      +    $objectDefinition = $this->getNewObjectDefinition($beanName);
      +
      +    $scope     = $objectDefinition->getScope();
      +    $alias     = $objectDefinition->getAlias();
      +    $className = $objectDefinition->getClassName();
      +
      +    // Cache reflection class info
      +    Reflections::cache($className);
      +
      +    // Before initialize bean
      +    $this->beforeInit($beanName, $className, $objectDefinition);
      +
      +    $constructArgs   = [];
      +    $constructInject = $objectDefinition->getConstructorInjection();
      +    if ($constructInject !== null) {
      +        $constructArgs = $this->getConstructParams($constructInject, $id);
      +    }
      +
      +    $propertyInjects = $objectDefinition->getPropertyInjections();
      +
      +    // Proxy class
      +    if ($this->handler) {
      +        $className = $this->handler->classProxy($className);
      +    }
      +
      +    $reflectionClass = new ReflectionClass($className);
      +    $reflectObject   = $this->newInstance($reflectionClass, $constructArgs);
      +
      +    // Inject properties values
      +    $this->newProperty($reflectObject, $reflectionClass, $propertyInjects, $id);
      +
      +    // Alias
      +    if (!empty($alias)) {
      +        $this->aliases[$alias] = $beanName;
      +    }
      +
      +    // Call init method if exist
      +    if ($reflectionClass->hasMethod(self::INIT_METHOD)) {
      +        $reflectObject->{self::INIT_METHOD}();
      +    }
      +
      +    return $this->setNewBean($beanName, $scope, $reflectObject, $id);
      +}
      +

      通过反射实例化Bean对应的类,注册对应的属性。

      +

      如果类存在self::INIT_METHOD方法,执行此方法。

      +
      /**
      +    * @param string $beanName
      +    * @param string $scope
      +    * @param object $object
      +    * @param string $id
      +    *
      +    * @return object
      +    */
      +private function setNewBean(string $beanName, string $scope, $object, string $id = '')
      +{
      +    switch ($scope) {
      +        case Bean::SINGLETON: // Singleton
      +            $this->singletonPool[$beanName] = $object;
      +            break;
      +        case Bean::PROTOTYPE:
      +            $this->prototypePool[$beanName] = $object;
      +            // Clone it
      +            $object = clone $object;
      +            break;
      +        case Bean::REQUEST:
      +            $this->requestPool[$id][$beanName] = $object;
      +            break;
      +        case Bean::SESSION:
      +            $this->sessionPool[$id][$beanName] = $object;
      +            break;
      +    }
      +
      +    return $object;
      +}
      +

      setNewBean方法,根据对应的scope信息,将实例化后的反射类注册到对应的类属性上。

      +

      到这里BeanProcessor类就执行完了。

      +]]>
      +
      + + Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + Thu, 29 Aug 2019 19:11:04 +0800 + https://liudon.com/posts/swoft-anaotion-processor-analysis/ + <p>上一篇介绍了,<code>SwoftApplication</code>里定义了6个Processor对象。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>protected function processors(): array +</span></span><span style="display:flex;"><span> { +</span></span><span style="display:flex;"><span> return [ +</span></span><span style="display:flex;"><span> new EnvProcessor($this), +</span></span><span style="display:flex;"><span> new ConfigProcessor($this), +</span></span><span style="display:flex;"><span> new AnnotationProcessor($this), +</span></span><span style="display:flex;"><span> new BeanProcessor($this), +</span></span><span style="display:flex;"><span> new EventProcessor($this), +</span></span><span style="display:flex;"><span> new ConsoleProcessor($this), +</span></span><span style="display:flex;"><span> ]; +</span></span><span style="display:flex;"><span> } +</span></span></code></pre></div><p>所有的Processor实现都在<code>framework\src\Processor</code>目录下。</p> + 上一篇介绍了,SwoftApplication里定义了6个Processor对象。

      +
      protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      所有的Processor实现都在framework\src\Processor目录下。

      +
        +
      1. +

        EnvProcessor,运行环境检查。

        +
      2. +
      3. +

        ConfigProcessor,配置相关。

        +
      4. +
      5. +

        AnnotationProcessor,注解相关。

        +
      6. +
      7. +

        BeanProcessor,Bean相关。

        +
      8. +
      9. +

        EventProcessor,事件相关。

        +
      10. +
      11. +

        ConsoleProcessor,命令行输入相关。

        +
      12. +
      +

      今天先讲一下AnnotationProcessor这个模块的实现。

      +
      <?php declare(strict_types=1);
      +
      +namespace Swoft\Processor;
      +
      +use Exception;
      +use Swoft\Annotation\AnnotationRegister;
      +use Swoft\Log\Helper\CLog;
      +
      +/**
      + * Annotation processor
      + * @since 2.0
      + */
      +class AnnotationProcessor extends Processor
      +{
      +    /**
      +     * Handle annotation
      +     *
      +     * @return bool
      +     * @throws Exception
      +     */
      +    public function handle(): bool
      +    {
      +        if (!$this->application->beforeAnnotation()) {
      +            CLog::warning('Stop annotation processor by beforeAnnotation return false');
      +            return false;
      +        }
      +
      +        $app = $this->application;
      +
      +        // Find AutoLoader classes. Parse and collect annotations.
      +        AnnotationRegister::load([
      +            'inPhar'               => \IN_PHAR,
      +            'basePath'             => $app->getBasePath(),
      +            'notifyHandler'        => [$this, 'notifyHandler'],
      +            'disabledAutoLoaders'  => $app->getDisabledAutoLoaders(),
      +            'disabledPsr4Prefixes' => $app->getDisabledPsr4Prefixes(),
      +        ]);
      +
      +        $stats = AnnotationRegister::getClassStats();
      +
      +        CLog::info(
      +            'Annotations is scanned(autoloader %d, annotation %d, parser %d)',
      +            $stats['autoloader'],
      +            $stats['annotation'],
      +            $stats['parser']
      +        );
      +
      +        return $this->application->afterAnnotation();
      +    }
      +
      +    /**
      +     * @param string $type
      +     * @param string $target
      +     * @see \Swoft\Annotation\Resource\AnnotationResource::load()
      +     */
      +    public function notifyHandler(string $type, $target): void
      +    {
      +        switch ($type) {
      +            case 'excludeNs':
      +                CLog::debug('Exclude namespace %s', $target);
      +                break;
      +            case 'noLoaderFile':
      +                CLog::debug('No autoloader on %s', $target);
      +                break;
      +            case 'noLoaderClass':
      +                CLog::debug('Autoloader class not exist %s', $target);
      +                break;
      +            case 'findLoaderClass':
      +                CLog::debug('Find autoloader %s', $target);
      +                break;
      +            case 'addLoaderClass':
      +                CLog::debug('Parse autoloader %s', $target);
      +                break;
      +            case 'noExistClass':
      +                CLog::debug('Skip interface or trait %s', $target);
      +                break;
      +        }
      +    }
      +}
      +

      核心逻辑调用AnnotationRegister类的load方法,定义如下。

      +
      /**
      +    * Load annotation class
      +    *
      +    * @param array $config
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public static function load(array $config = []): void
      +{
      +    $resource = new AnnotationResource($config);
      +    $resource->load();
      +}
      +

      这里又调用了AnnotationResource类的load方法,定义如下。

      +
      /**
      +    * Load annotation resource by find ClassLoader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +public function load(): void
      +{
      +    $prefixDirsPsr4 = $this->classLoader->getPrefixesPsr4();
      +
      +    foreach ($prefixDirsPsr4 as $ns => $paths) {
      +        // Only scan namespaces
      +        if ($this->onlyNamespaces && !in_array($ns, $this->onlyNamespaces, true)) {
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // It is excluded psr4 prefix
      +        if ($this->isExcludedPsr4Prefix($ns)) {
      +            AnnotationRegister::registerExcludeNs($ns);
      +            $this->notify('excludeNs', $ns);
      +            continue;
      +        }
      +
      +        // Find package/component loader class
      +        foreach ($paths as $path) {
      +            $loaderFile = $this->getAnnotationClassLoaderFile($path);
      +            if (!file_exists($loaderFile)) {
      +                $this->notify('noLoaderFile', $this->clearBasePath($path), $loaderFile);
      +                continue;
      +            }
      +
      +            $loaderClass = $this->getAnnotationLoaderClassName($ns);
      +            if (!class_exists($loaderClass)) {
      +                $this->notify('noLoaderClass', $loaderClass);
      +                continue;
      +            }
      +
      +            $loaderObject = new $loaderClass();
      +            if (!$loaderObject instanceof LoaderInterface) {
      +                $this->notify('invalidLoader', $loaderFile);
      +                continue;
      +            }
      +
      +            $this->notify('findLoaderClass', $this->clearBasePath($loaderFile));
      +
      +            // If is disable, will skip scan annotation classes
      +            if (!isset($this->disabledAutoLoaders[$loaderClass])) {
      +                AnnotationRegister::registerAutoLoaderFile($loaderFile);
      +                $this->notify('addLoaderClass', $loaderClass);
      +                $this->loadAnnotation($loaderObject);
      +            }
      +
      +            // Storage auto loader to register
      +            AnnotationRegister::addAutoLoader($ns, $loaderObject);
      +        }
      +    }
      +}
      +

      通过getPrefixesPsr4方法获取所有自动加载的命名空间和目录,遍历目录下的AutoLoader.php文件。

      +

      通过registerAutoLoaderFile注册自动加载文件到AnnotationRegister对象上。

      +

      然后调用了loadAnnotation方法,传入的是一个autoload对象。

      +
      /**
      +    * Load annotations from an component loader config.
      +    *
      +    * @param LoaderInterface $loader
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function loadAnnotation(LoaderInterface $loader): void
      +{
      +    $nsPaths = $loader->getPrefixDirs();
      +
      +    foreach ($nsPaths as $ns => $path) {
      +        $iterator = DirectoryHelper::recursiveIterator($path);
      +
      +        /* @var SplFileInfo $splFileInfo */
      +        foreach ($iterator as $splFileInfo) {
      +            $filePath = $splFileInfo->getPathname();
      +            // $splFileInfo->isDir();
      +            if (is_dir($filePath)) {
      +                continue;
      +            }
      +
      +            $fileName  = $splFileInfo->getFilename();
      +            $extension = $splFileInfo->getExtension();
      +
      +            if ($this->loaderClassSuffix !== $extension || strpos($fileName, '.') === 0) {
      +                continue;
      +            }
      +
      +            // It is exclude filename
      +            if (isset($this->excludedFilenames[$fileName])) {
      +                AnnotationRegister::registerExcludeFilename($fileName);
      +                continue;
      +            }
      +
      +            $suffix    = sprintf('.%s', $this->loaderClassSuffix);
      +            $pathName  = str_replace([$path, '/', $suffix], ['', '\\', ''], $filePath);
      +            $className = sprintf('%s%s', $ns, $pathName);
      +
      +            // Fix repeat included file bug
      +            $autoload = in_array($filePath, $this->includedFiles, true);
      +
      +            // Will filtering: interfaces and traits
      +            if (!class_exists($className, !$autoload)) {
      +                $this->notify('noExistClass', $className);
      +                continue;
      +            }
      +
      +            // Parse annotation
      +            $this->parseAnnotation($ns, $className);
      +        }
      +    }
      +}
      +

      通过getPrefixDirs获取当前命名空间的目录,然后通过recursiveIterator遍历目录下的文件。

      +

      排除目录和非.php结尾的文件,最后会调用parseAnnotation方法。

      +
      /**
      +    * Parser annotation
      +    *
      +    * @param string $namespace
      +    * @param string $className
      +    *
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseAnnotation(string $namespace, string $className): void
      +{
      +    // Annotation reader
      +    $reflectionClass = new ReflectionClass($className);
      +
      +    // Fix ignore abstract
      +    if ($reflectionClass->isAbstract()) {
      +        return;
      +    }
      +    $oneClassAnnotation = $this->parseOneClassAnnotation($reflectionClass);
      +
      +    if (!empty($oneClassAnnotation)) {
      +        AnnotationRegister::registerAnnotation($namespace, $className, $oneClassAnnotation);
      +    }
      +}
      +

      这里调用了parseOneClassAnnotation方法。

      +
      /**
      +    * Parse an class annotation
      +    *
      +    * @param ReflectionClass $reflectionClass
      +    *
      +    * @return array
      +    * @throws AnnotationException
      +    * @throws ReflectionException
      +    */
      +private function parseOneClassAnnotation(ReflectionClass $reflectionClass): array
      +{
      +    // Annotation reader
      +    $reader    = new AnnotationReader();
      +    $className = $reflectionClass->getName();
      +
      +    $oneClassAnnotation = [];
      +    $classAnnotations   = $reader->getClassAnnotations($reflectionClass);
      +
      +    // Register annotation parser
      +    foreach ($classAnnotations as $classAnnotation) {
      +        if ($classAnnotation instanceof AnnotationParser) {
      +            $this->registerParser($className, $classAnnotation);
      +
      +            return [];
      +        }
      +    }
      +
      +    // Class annotation
      +    if (!empty($classAnnotations)) {
      +        $oneClassAnnotation['annotation'] = $classAnnotations;
      +        $oneClassAnnotation['reflection'] = $reflectionClass;
      +    }
      +
      +    // Property annotation
      +    $reflectionProperties = $reflectionClass->getProperties();
      +    foreach ($reflectionProperties as $reflectionProperty) {
      +        $propertyName        = $reflectionProperty->getName();
      +        $propertyAnnotations = $reader->getPropertyAnnotations($reflectionProperty);
      +
      +        if (!empty($propertyAnnotations)) {
      +            $oneClassAnnotation['properties'][$propertyName]['annotation'] = $propertyAnnotations;
      +            $oneClassAnnotation['properties'][$propertyName]['reflection'] = $reflectionProperty;
      +        }
      +    }
      +
      +    // Method annotation
      +    $reflectionMethods = $reflectionClass->getMethods();
      +    foreach ($reflectionMethods as $reflectionMethod) {
      +        $methodName        = $reflectionMethod->getName();
      +        $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);
      +
      +        if (!empty($methodAnnotations)) {
      +            $oneClassAnnotation['methods'][$methodName]['annotation'] = $methodAnnotations;
      +            $oneClassAnnotation['methods'][$methodName]['reflection'] = $reflectionMethod;
      +        }
      +    }
      +
      +    $parentReflectionClass = $reflectionClass->getParentClass();
      +    if ($parentReflectionClass !== false) {
      +        $parentClassAnnotation = $this->parseOneClassAnnotation($parentReflectionClass);
      +        if (!empty($parentClassAnnotation)) {
      +            $oneClassAnnotation['parent'] = $parentClassAnnotation;
      +        }
      +    }
      +
      +    return $oneClassAnnotation;
      +}
      +

      这里就是解析注解了,可以看到分别有类注解、属性注解和方法注解三类。

      +

      这里注意这一段代码。

      +
      // Register annotation parser
      +foreach ($classAnnotations as $classAnnotation) {
      +    if ($classAnnotation instanceof AnnotationParser) {
      +        $this->registerParser($className, $classAnnotation);
      +
      +        return [];
      +    }
      +}
      +

      遍历注解类,如果注解属于AnnotationParser实例,这里调用registerParser进行注册。

      +
      /**
      +    * @param string $annotationClass
      +    * @param string $parserClassName
      +    */
      +public static function registerParser(string $annotationClass, string $parserClassName): void
      +{
      +    self::$classStats['parser']++;
      +    self::$parsers[$annotationClass] = $parserClassName;
      +}
      +

      回到上一个方法,解析完后,又调用了AnnotationRegister类的registerAnnotation方法进行注册。

      +
      /**
      +    * @param string $loadNamespace
      +    * @param string $className
      +    * @param array  $classAnnotation
      +    */
      +public static function registerAnnotation(string $loadNamespace, string $className, array $classAnnotation): void
      +{
      +    self::$classStats['annotation']++;
      +    self::$annotations[$loadNamespace][$className] = $classAnnotation;
      +}
      +

      至此,整个AnnotationProcessor加载完毕,这里AnnotationRegister类里会有annotationsparsers两个属性,这个信息在后面的BeanProcessor里还会用到。

      +]]>
      +
      + + Swoft 框架运行分析(一) + https://liudon.com/posts/swoft-execution-analysis/ + Thu, 29 Aug 2019 17:22:28 +0800 + https://liudon.com/posts/swoft-execution-analysis/ + <blockquote> +<p>Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。</p> +</blockquote> +<p>以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。</p> +<p>刚开始看的时候,感觉自己像个原始人,完全看不懂。</p> + +

      Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。

      + +

      以前一直都是用的原生swoole框架,最近有时间研究了下衍生的Swoft框架。

      +

      刚开始看的时候,感觉自己像个原始人,完全看不懂。

      +

      官方文档没有介绍Swoft的实现,网上的一些文章跟当前版本代码已经不一致了。

      +

      自己花了一周时间,终于梳理清楚了,看完更觉得自己是个原始人了。

      +

      使用的框架组件版本为:

      +
      swoft-2.0.5
      +swoft-component-2.0.5
      +

      这里以Swoft启动http server为例。

      +
      php bin/swoft http:start
      +

      执行上述命令,启动http server。

      +

      这里执行的是bin/swoft文件。

      +
      #!/usr/bin/env php
      +<?php declare(strict_types=1);
      +
      +// Bootstrap
      +require_once __DIR__ . '/bootstrap.php';
      +
      +Swoole\Coroutine::set([
      +    'max_coroutine' => 300000,
      +]);
      +
      +// Run application
      +(new \App\Application())->run();
      +

      这里引入bootstrap.php文件,引入composer自动加载文件。

      +
      <?php
      +// Composer autoload
      +require_once dirname(__DIR__) . '/vendor/autoload.php';
      +

      然后执行Swoft\App\Application类下的run方法。

      +
      <?php declare(strict_types=1);
      +
      +namespace App;
      +
      +use Swoft\SwoftApplication;
      +use function date_default_timezone_set;
      +
      +/**
      + * Class Application
      + *
      + * @since 2.0
      + */
      +class Application extends SwoftApplication
      +{
      +    protected function beforeInit(): void
      +    {
      +        parent::beforeInit();
      +
      +        date_default_timezone_set('Asia/Shanghai');
      +    }
      +}
      +

      这里继承了Swoft\SwoftApplication类,这里只粘贴了部分代码。

      +
      /**
      + * Swoft application
      + *
      + * @since 2.0
      + */
      +class SwoftApplication implements SwoftInterface, ApplicationInterface
      +{
      +
      +    /**
      +     * Class constructor.
      +     *
      +     * @param array $config
      +     */
      +    public function __construct(array $config = [])
      +    {
      +        // Check runtime env
      +        SwoftHelper::checkRuntime();
      +
      +        // Storage as global static property.
      +        Swoft::$app = $this;
      +
      +        // Before init
      +        $this->beforeInit();
      +
      +        // Init console logger
      +        $this->initCLogger();
      +
      +        // Can setting properties by array
      +        if ($config) {
      +            ObjectHelper::init($this, $config);
      +        }
      +
      +        // Init application
      +        $this->init();
      +
      +        CLog::info('Project path is <info>%s</info>', $this->basePath);
      +
      +        // After init
      +        $this->afterInit();
      +    }
      +
      +    protected function init(): void
      +    {
      +        // Init system path aliases
      +        $this->findBasePath();
      +        $this->setSystemAlias();
      +
      +        $processors = $this->processors();
      +
      +        $this->processor = new ApplicationProcessor($this);
      +        $this->processor->addFirstProcessor(...$processors);
      +    }
      +
      +    /**
      +     * Run application
      +     */
      +    public function run(): void
      +    {
      +        if (!$this->beforeRun()) {
      +            return;
      +        }
      +
      +        $this->processor->handle();
      +    }
      +
      +    /**
      +     * @return ProcessorInterface[]
      +     */
      +    protected function processors(): array
      +    {
      +        return [
      +            new EnvProcessor($this),
      +            new ConfigProcessor($this),
      +            new AnnotationProcessor($this),
      +            new BeanProcessor($this),
      +            new EventProcessor($this),
      +            new ConsoleProcessor($this),
      +        ];
      +    }
      +

      __construct方法里检查运行环境,初始化日志组件,然后调用了init方法。

      +

      init方法里声明了processor对象。

      +

      processors方法定义了Swoft框架的6个Processor对象。

      +

      run方法里直接调用processor对象的handler方法。

      +
      <?php
      +
      +namespace Swoft\Processor;
      +
      +use Swoft\Stdlib\Helper\ArrayHelper;
      +use function get_class;
      +
      +/**
      + * Application processor
      + * @since 2.0
      + */
      +class ApplicationProcessor extends Processor
      +{
      +    /**
      +     * @var ProcessorInterface[]
      +     */
      +    private $processors = [];
      +
      +    /**
      +     * Handle application processors
      +     */
      +    public function handle(): bool
      +    {
      +        $disabled = $this->application->getDisabledProcessors();
      +
      +        foreach ($this->processors as $processor) {
      +            $class = get_class($processor);
      +
      +            // If is disabled, skip handle.
      +            if (isset($disabled[$class])) {
      +                continue;
      +            }
      +
      +            $processor->handle();
      +        }
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add first processor
      +     *
      +     * @param Processor[] $processor
      +     * @return bool
      +     */
      +    public function addFirstProcessor(Processor ...$processor): bool
      +    {
      +        array_unshift($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add last processor
      +     *
      +     * @param Processor[] $processor
      +     *
      +     * @return bool
      +     */
      +    public function addLastProcessor(Processor ...$processor): bool
      +    {
      +        array_push($this->processors, ... $processor);
      +
      +        return true;
      +    }
      +
      +    /**
      +     * Add processors
      +     *
      +     * @param int         $index
      +     * @param Processor[] $processors
      +     *
      +     * @return bool
      +     */
      +    public function addProcessor(int $index, Processor  ...$processors): bool
      +    {
      +        ArrayHelper::insert($this->processors, $index, ...$processors);
      +
      +        return true;
      +    }
      +}
      +

      addFirstProcessor方法把process对象赋值给$this->processors

      +

      handle方法遍历processors对象,循环执行handle方法。

      +

      Swoft的核心逻辑都是靠上面定义的6个Processor模块实现的,接下来一个一个分析。

      +]]>
      +
      +
      +
      diff --git a/tags/swoft/page/1/index.html b/tags/swoft/page/1/index.html new file mode 100644 index 000000000..fc2d3d58f --- /dev/null +++ b/tags/swoft/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/swoft/ + \ No newline at end of file diff --git a/tags/thinkpad/index.html b/tags/thinkpad/index.html new file mode 100644 index 000000000..03b914493 --- /dev/null +++ b/tags/thinkpad/index.html @@ -0,0 +1,8 @@ +Thinkpad | 流动 +

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git a/tags/thinkpad/index.xml b/tags/thinkpad/index.xml new file mode 100644 index 000000000..b1d023346 --- /dev/null +++ b/tags/thinkpad/index.xml @@ -0,0 +1,79 @@ + + + + Thinkpad on 流动 + https://liudon.com/tags/thinkpad/ + Recent content in Thinkpad on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 22 Apr 2022 08:04:18 +0800 + + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      +
      +
      diff --git a/tags/thinkpad/page/1/index.html b/tags/thinkpad/page/1/index.html new file mode 100644 index 000000000..44f4d45e9 --- /dev/null +++ b/tags/thinkpad/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/thinkpad/ + \ No newline at end of file diff --git a/tags/twikoo/index.html b/tags/twikoo/index.html new file mode 100644 index 000000000..7a074a669 --- /dev/null +++ b/tags/twikoo/index.html @@ -0,0 +1,7 @@ +Twikoo | 流动 +

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon
      \ No newline at end of file diff --git a/tags/twikoo/index.xml b/tags/twikoo/index.xml new file mode 100644 index 000000000..ed2579fe1 --- /dev/null +++ b/tags/twikoo/index.xml @@ -0,0 +1,216 @@ + + + + Twikoo on 流动 + https://liudon.com/tags/twikoo/ + Recent content in Twikoo on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 19 Oct 2023 19:46:32 +0800 + + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      +
      +
      diff --git a/tags/twikoo/page/1/index.html b/tags/twikoo/page/1/index.html new file mode 100644 index 000000000..acfbabb93 --- /dev/null +++ b/tags/twikoo/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/twikoo/ + \ No newline at end of file diff --git a/tags/vercel/index.html b/tags/vercel/index.html new file mode 100644 index 000000000..611acd2c9 --- /dev/null +++ b/tags/vercel/index.html @@ -0,0 +1,7 @@ +Vercel | 流动 +

      在Netlify上部署Twikoo评论系统

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。 +2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。 +...

      2023-10-19 · 5 min · 2232 words · Liudon
      \ No newline at end of file diff --git a/tags/vercel/index.xml b/tags/vercel/index.xml new file mode 100644 index 000000000..3f7855a8c --- /dev/null +++ b/tags/vercel/index.xml @@ -0,0 +1,216 @@ + + + + Vercel on 流动 + https://liudon.com/tags/vercel/ + Recent content in Vercel on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 19 Oct 2023 19:46:32 +0800 + + + 在Netlify上部署Twikoo评论系统 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + Thu, 19 Oct 2023 19:46:32 +0800 + https://liudon.com/posts/deploy-twikoo-on-netlify/ + <blockquote> +<p>在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。</p> +</blockquote> +<p><em>2024年7月30日更新:因为<a href="https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting">Github接口策略调整</a>,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。</em></p> + +

      在本篇文章里,我会介绍如何在Netlify上部署Twikoo评论系统,如何接入到静态博客Hugo,以及如何实现Twikoo系统版本自动更新。

      + +

      2024年7月30日更新:因为Github接口策略调整,原有的匿名通过接口获取版本号方法失效,已更改为带token方式请求接口获取版本号,详见workflow里Get twikoo version步骤配置。

      +

      背景

      +

      博客之前通过Vercel部署了Twikoo评论系统,但是最近发现加载很慢。

      +

      Twikoo官网文档NetlifyVercel国内访问会更优一些,于是搞了一把迁移。

      +

      迁移过程中遇到了一些问题,网上搜了一番,发现Netlify下部署Twikoo的信息很少,这篇文章我会介绍整个操作过程。

      +

      部署

      +

      参考官网文档,部署即可。

      +

      这里一开始Fork成了https://github.com/twikoojs/twikoo,导致部署后访问404。

      +

      注意,正确的仓库是https://github.com/twikoojs/twikoo-netlify

      +

      + +Netlify部署Twikoo + + +

      +

      使用同一个MongoDB,配置原来的地址,这样已有的评论也不会丢。

      +

      部署后,通过https://comment.liudon.com访问,返回200。

      +

      但是访问https://liudon.com,提示CORS跨域错误,搜索一番网上资料也没找到相关信息。

      +

      + +CORS报错 + + +

      +

      一般跨域错误都是返回头里缺少跨域相关的字段导致,因此决定使用Netlify来新增返回头规避。

      +

      仓库下新增netlify.toml文件,针对根目录返回跨域头,内容如下。

      +
      [[headers]]
      +  # Define which paths this specific [[headers]] block will cover.
      +  for = "/"
      +    [headers.values]
      +    Access-Control-Allow-Origin = "*"
      +    Access-Control-Allow-Headers = "Content-Type"
      +    Access-Control-Allow-Methods = "*"
      +

      再次刷新页面,报错又变成了404,但是直接访问https://comment.liudon.com地址是返回的200。

      +

      + +404报错 + + +

      +

      经过一番定位,发现了问题所在:

      +

      twikoo-netlify库根目录地址是通过Location跳转到的/.netlify/functions/twikoo接口,/.netlify/functions/twikoo才是真正处理的接口。

      +

      + +twikoo-netlify根目录返回 + + +

      +

      vercel-netlify库是通过rewrite将所有请求转发到了/api/index接口。

      +

      + +twikoo-vercel转发 + + +

      +

      博客里写的Twikoo环境id填的是https://comment.liudon.com,更换为Netlify后,需要更换环境id,补齐后面的/.netlify/functions/twikoo

      +

      + +twikoo环境id + + +

      +

      因为Netlify也支持Rewrite转发,所以决定还是通过netlify.toml文件增加转发配置解决,内容如下:

      +
      [[redirects]]
      +  from = "/"
      +  to = "/.netlify/functions/twikoo"
      +  status = 200
      +  force = true
      +

      注意一定不要漏了force参数,因为根目录文件是存在的,不带这个参数的话Rewrite是不生效的,必须指定这个。

      +

      这下访问彻底ok了。

      +

      不过很快又发现另外一个问题,看到别人博客上Twikoo版本已经是1.6.22,自己的还是1.5.11

      +

      搜索一番后,Twikoo已经给出了更新操作:

      +
      针对 Netlify 部署的更新方式
      +
      +1. 登录 Github,找到部署时 fork 到自己账号下的名为 twikoo-netlify 的仓库
      +2. 打开 package.json,点击编辑
      +3. 将 "twikoo-vercel": "latest" 其中的 latest 修改为最新版本号。点击 Commit changes
      +4. 部署会自动触发
      +

      不过这样操作,每次版本更新,都需要手动去改一下package.json文件的版本,重新构建。

      +

      对于一个程序员,我们的追求就是自动化。

      +

      自动版本更新

      +
        +
      1. 服务端版本自动更新
      2. +
      +

      这里利用Github Actions定时任务,通过接口拉取twikoo最新的版本,然后更新到package.json文件,从而实现版本自动更新。

      +

      在自己twikoo-netlify仓库下,新增Actions,代码如下:

      +
      # This is a basic workflow to help you get started with Actions
      +
      +name: CI
      +
      +# Controls when the workflow will run
      +on:
      +  # Triggers the workflow on push or pull request events but only for the "main" branch
      +  push:
      +    branches: [ "main" ]
      +  pull_request:
      +    branches: [ "main" ]
      +
      +  # Allows you to run this workflow manually from the Actions tab
      +  workflow_dispatch:
      +  schedule:
      +    - cron: '0 2 * * *' # 每天定时2点执行一次
      +
      +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
      +jobs:
      +  # This workflow contains a single job called "build"
      +  build:
      +    # The type of runner that the job will run on
      +    runs-on: ubuntu-latest
      +  
      +    permissions:      
      +      contents: write
      +
      +    # Steps represent a sequence of tasks that will be executed as part of the job
      +    steps:
      +      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      +      - uses: actions/checkout@v3
      +
      +      # Runs a single command using the runners shell
      +      - name: update version
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +        run: |
      +          response=$(curl -sf -H "Authorization: token $github_token" -H "Accept: application/vnd.github+json" https://api.github.com/repos/twikoojs/twikoo/releases/latest)
      +          version=$(echo $response | jq -r '.tag_name')
      +          if [ -n "$version" ]; then
      +            sed -i "s/\"twikoo-netlify\": \".*\"/\"twikoo-netlify\": \"$version\"/" package.json
      +          fi
      +        shell: bash
      +        
      +      # Runs a set of commands using the runners shell
      +      - name: Commit changes
      +        uses: EndBug/add-and-commit@v9
      +        env:
      +          github_token: ${{ secrets.TOKEN }}
      +          add: .
      +

      这里会把修改后的package.json文件提交到仓库,所以需要申请一个Token,可以参考我上一篇文章申请,然后添加到仓库变量里。

      +
        +
      1. 博客引用版本自动更新
      2. +
      +

      这里一开始想到在前端去查服务端最新版本号,然后引用对应版本的js文件来实现。

      +

      但是这样就会导致每次页面加载都要去查一次版本,会导致加载时间变长。

      +

      因此还是决定在服务端部署时,获取最新版本号更新服务。

      +
        +
      • 引用版本配置化
      • +
      +

      comments.html文件修改:

      +
      <script src="https://cdn.staticfile.org/twikoo/{{ .Site.Params.twikoo.version }}/twikoo.all.min.js">
      +

      config.tml配置修改:

      +
      params:
      +  env: production # to enable google analytics, opengraph, twitter-cards and schema.
      +  
      +  ... # 其他配置
      +
      +  assets:
      +    disableHLJS: true # to disable highlight.js
      +    
      +  twikoo:
      +    version: 1.5.11 # 配置twikoo版本号
      +
        +
      • 获取最新版本号部署
      • +
      +

      Actions新增步骤:

      +
          - name: Setup Hugo
      +    uses: peaceiris/actions-hugo@v2
      +    with:
      +        hugo-version: 'latest'
      +
      +    ####
      +    - name: Get twikoo version
      +    id: twikoo
      +    run: |
      +        version=$(curl -s https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json | jq -r '.dependencies."twikoo-netlify"')
      +        echo "Twikoo version: $version"
      +        echo "twikoo_version=$version" >> $GITHUB_OUTPUT
      +
      +    - name: Update config.yml version
      +    uses: fjogeleit/yaml-update-action@main
      +    with:
      +        valueFile: 'config.yml'
      +        propertyPath: 'params.twikoo.version'
      +        value: ${{ steps.twikoo.outputs.twikoo_version }}
      +        commitChange: true
      +    ####
      +        
      +    - name: Build
      +    run: hugo --gc --minify --cleanDestinationDir 
      +

      ####内代码即为获取版本号,更新config.tml版本号逻辑,然后再进行hugo部署。

      +

      需要将https://raw.githubusercontent.com/Liudon/twikoo-netlify/main/package.json这个url里的Liudon/twikoo-netlify改为你的仓库名。

      +

      这下后面Twikoo官方更新版本,博客的Twikoo也会跟着自动更新。

      +]]>
      +
      +
      +
      diff --git a/tags/vercel/page/1/index.html b/tags/vercel/page/1/index.html new file mode 100644 index 000000000..b49d7b7e0 --- /dev/null +++ b/tags/vercel/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/vercel/ + \ No newline at end of file diff --git a/tags/weibo/index.html b/tags/weibo/index.html new file mode 100644 index 000000000..c0a600da0 --- /dev/null +++ b/tags/weibo/index.html @@ -0,0 +1,8 @@ +Weibo | 流动 +

      利用Github Actions定时抓取微博

      背景 在微博上关注了一些用户,比如tk教主,月风。 +但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。 +实现方案 整体思路:利用Github Actions的Scheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。 +...

      2023-10-07 · 2 min · 823 words · Liudon
      \ No newline at end of file diff --git a/tags/weibo/index.xml b/tags/weibo/index.xml new file mode 100644 index 000000000..894b52088 --- /dev/null +++ b/tags/weibo/index.xml @@ -0,0 +1,117 @@ + + + + Weibo on 流动 + https://liudon.com/tags/weibo/ + Recent content in Weibo on 流动 + Hugo -- 0.134.3 + zh-cn + Sat, 07 Oct 2023 13:17:57 +0800 + + + 利用Github Actions定时抓取微博 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + Sat, 07 Oct 2023 13:17:57 +0800 + https://liudon.com/posts/using-github-actions-to-schedule-weibo-scraping/ + <h4 id="背景">背景</h4> +<p>在微博上关注了一些用户,比如<a href="https://weibo.com/u/1401527553">tk教主</a>,<a href="https://weibo.com/u/1670659923">月风</a>。</p> +<p>但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。</p> +<h4 id="实现方案">实现方案</h4> +<p><strong>整体思路:利用<code>Github Actions</code>的<code>Scheduled</code>任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。</strong></p> + 背景 +

      在微博上关注了一些用户,比如tk教主月风

      +

      但是有些内容过段时间不可见了,所以希望可以定时抓取微博归档备份下来。

      +

      实现方案

      +

      整体思路:利用Github ActionsScheduled任务,定时执行抓取shell脚本,将内容保存到文件,提交到Github仓库。

      +
        +
      1. +

        新建仓库,比如weibo_archive

        +
      2. +
      3. +

        添加抓取脚本,完整代码

        +

        这里用到微博两个接口:

        +
        // 抓取某个用户最新的10条微博数据,返回里有每条微博的id,这里如果内容过长的话会被截断
        +https://m.weibo.cn/api/container/getIndex?jumpfrom=weibocom&type=uid&value=$uid&containerid=107603$uid
        +
        +// 根据微博id,抓取微博完整的内容
        +https://m.weibo.cn/statuses/extend?id=$id
        +
      4. +
      5. +

        添加环境变量。

        +
          +
        • +

          进入个人设置->Developer Settings->Personal access tokens->Tokens (classic),创建新的Token,记下对应的值。

          +
        • +
        • +

          进入第一步创建仓库的配置页,点击Secrets and variables下的Actions

          +

          切到Secret目录,创建新的Secret变量,名称为TOKEN,值为前一步记录的值;切到Variables目录,创建新的Variables变量,名称为WEIBO_UIDS,值为你需要抓取的微博用户id,多个用户的话以|分割。

          +
        • +
        +
      6. +
      7. +

        添加定时任务,完整yaml文件如下。

        +
        # This is a basic workflow to help you get started with Actions
        +
        +name: CI
        +
        +# Controls when the workflow will run
        +on:
        +# Triggers the workflow on push or pull request events but only for the "main" branch
        +push:
        +    branches: [ "main" ]
        +pull_request:
        +    branches: [ "main" ]
        +
        +# Allows you to run this workflow manually from the Actions tab
        +workflow_dispatch:
        +schedule:
        +    - cron: '*/5 * * * *'
        +
        +# A workflow run is made up of one or more jobs that can run sequentially or in parallel
        +jobs:
        +# This workflow contains a single job called "build"
        +build:
        +    # The type of runner that the job will run on
        +    runs-on: ubuntu-latest
        +
        +    permissions:      
        +    contents: write
        +
        +    # Steps represent a sequence of tasks that will be executed as part of the job
        +    steps:
        +    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
        +    - uses: actions/checkout@v3
        +
        +    # Runs a single command using the runners shell
        +    - name: archive weibo
        +        run: |
        +        chmod +x ./weibo_archive.sh
        +        ./weibo_archive.sh
        +        shell: bash
        +        env:
        +        weibo_uids: ${{ vars.weibo_uids }}
        +
        +    # Runs a set of commands using the runners shell
        +    - name: Commit changes
        +        uses: EndBug/add-and-commit@v9
        +        env:
        +        github_token: ${{ secrets.TOKEN }}
        +        add: .
        +
      8. +
      +

      效果

      +

      抓取后的内容,会按用户id分别保存到不同文件。

      +

      + +效果 + + +

      +

      不过这个方案有一个唯一的缺点,Github Actions定时任务时间粒度最小是5分钟,而且不能保证执行时间完全符合这个粒度,所以可能还是会有漏掉的内容。

      +
      +

      Note: The schedule event can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour. If the load is sufficiently high enough, some queued jobs may be dropped. To decrease the chance of delay, schedule your workflow to run at a different time of the hour.

      +
      +]]>
      +
      +
      +
      diff --git a/tags/weibo/page/1/index.html b/tags/weibo/page/1/index.html new file mode 100644 index 000000000..a6c28410e --- /dev/null +++ b/tags/weibo/page/1/index.html @@ -0,0 +1,2 @@ +https://liudon.com/tags/weibo/ + \ No newline at end of file diff --git "a/tags/\344\270\211\346\230\237/index.html" "b/tags/\344\270\211\346\230\237/index.html" new file mode 100644 index 000000000..b182c1d77 --- /dev/null +++ "b/tags/\344\270\211\346\230\237/index.html" @@ -0,0 +1,8 @@ +三星 | 流动 +

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git "a/tags/\344\270\211\346\230\237/index.xml" "b/tags/\344\270\211\346\230\237/index.xml" new file mode 100644 index 000000000..41eb5bb5e --- /dev/null +++ "b/tags/\344\270\211\346\230\237/index.xml" @@ -0,0 +1,79 @@ + + + + 三星 on 流动 + https://liudon.com/tags/%E4%B8%89%E6%98%9F/ + Recent content in 三星 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 22 Apr 2022 08:04:18 +0800 + + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      +
      +
      diff --git "a/tags/\344\270\211\346\230\237/page/1/index.html" "b/tags/\344\270\211\346\230\237/page/1/index.html" new file mode 100644 index 000000000..f9f20ff41 --- /dev/null +++ "b/tags/\344\270\211\346\230\237/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E4%B8%89%E6%98%9F/ + \ No newline at end of file diff --git "a/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.html" "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.html" new file mode 100644 index 000000000..13f5dceb5 --- /dev/null +++ "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.html" @@ -0,0 +1,9 @@ +个人技能 | 流动 +

      我的学车之路

      之前在2022年终总结提到过,我在练车考驾照。 +就在昨天,终于拿证了。👏👏👏 +咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶 +2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 2022年10月12日,科目一考试通过。 2022年10月22日,科目二模拟驾驶。 2022年11月13日,科目二第一次上车练习。 2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 2023年2月4日,年后驾校恢复培训,继续科目二练车。 2023年2月13日,科目二考试通过。 2023年3月11日,科目三上路练习。 2023年3月23日,上午科目三考试通过,下午科目四考试通过。 考试的时候,早上遇到临时交通管制,一直到9点40才开考。 考完回来,班车上的人说又管制不能考了。 班车拉回驾校,剩下的人中午加班考试。 终于不用再5点半起床赶班车了。🥱 +...

      2023-03-24 · 1 min · 375 words · Liudon
      \ No newline at end of file diff --git "a/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.xml" "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.xml" new file mode 100644 index 000000000..d1d09ea38 --- /dev/null +++ "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/index.xml" @@ -0,0 +1,73 @@ + + + + 个人技能 on 流动 + https://liudon.com/tags/%E4%B8%AA%E4%BA%BA%E6%8A%80%E8%83%BD/ + Recent content in 个人技能 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 24 Mar 2023 20:48:24 +0800 + + + 我的学车之路 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + Fri, 24 Mar 2023 20:48:24 +0800 + https://liudon.com/posts/my-journey-of-learning-to-drive/ + <p>之前在<a href="https://liudon.com/posts/review-2022/">2022年终总结</a>提到过,我在练车考驾照。</p> +<p>就在昨天,终于拿证了。👏👏👏</p> +<p>咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月12日,科目一考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年10月22日,科目二模拟驾驶。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月13日,科目二第一次上车练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月4日,年后驾校恢复培训,继续科目二练车。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年2月13日,科目二考试通过。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月11日,科目三上路练习。 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>2023年3月23日,上午科目三考试通过,下午科目四考试通过。 +</span></span><span style="display:flex;"><span>考试的时候,早上遇到临时交通管制,一直到9点40才开考。 +</span></span><span style="display:flex;"><span>考完回来,班车上的人说又管制不能考了。 +</span></span><span style="display:flex;"><span>班车拉回驾校,剩下的人中午加班考试。 +</span></span></code></pre></div><p>终于不用再5点半起床赶班车了。🥱</p> + 之前在2022年终总结提到过,我在练车考驾照。

      +

      就在昨天,终于拿证了。👏👏👏

      +

      咱也是可以上路开车的人了,虽然比别人晚了快10年才拿证。🐶

      +
      2022年6月11日,在海淀驾校报名,周末连续班,报名费5380元。
      +
      +2022年10月12日,科目一考试通过。
      +
      +2022年10月22日,科目二模拟驾驶。
      +
      +2022年11月13日,科目二第一次上车练习。
      +
      +2022年11月24日,疫情严重,驾校发通知,自11月25日暂停培训。
      +
      +2023年2月4日,年后驾校恢复培训,继续科目二练车。
      +
      +2023年2月13日,科目二考试通过。
      +
      +2023年3月11日,科目三上路练习。
      +
      +2023年3月23日,上午科目三考试通过,下午科目四考试通过。
      +考试的时候,早上遇到临时交通管制,一直到9点40才开考。
      +考完回来,班车上的人说又管制不能考了。
      +班车拉回驾校,剩下的人中午加班考试。
      +

      终于不用再5点半起床赶班车了。🥱

      +

      + +本本到手啦 + + +

      +]]>
      +
      +
      +
      diff --git "a/tags/\344\270\252\344\272\272\346\212\200\350\203\275/page/1/index.html" "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/page/1/index.html" new file mode 100644 index 000000000..b22eaa27f --- /dev/null +++ "b/tags/\344\270\252\344\272\272\346\212\200\350\203\275/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E4%B8%AA%E4%BA%BA%E6%8A%80%E8%83%BD/ + \ No newline at end of file diff --git "a/tags/\345\205\254\347\247\257\351\207\221/index.html" "b/tags/\345\205\254\347\247\257\351\207\221/index.html" new file mode 100644 index 000000000..877cd1c74 --- /dev/null +++ "b/tags/\345\205\254\347\247\257\351\207\221/index.html" @@ -0,0 +1,8 @@ +公积金 | 流动 +

      如何在北京公积金网站上修改婚姻状况

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告 +时间:2020年01月08日 +来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html +...

      2020-01-17 · 1 min · 438 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\205\254\347\247\257\351\207\221/index.xml" "b/tags/\345\205\254\347\247\257\351\207\221/index.xml" new file mode 100644 index 000000000..5c0cdb6f4 --- /dev/null +++ "b/tags/\345\205\254\347\247\257\351\207\221/index.xml" @@ -0,0 +1,88 @@ + + + + 公积金 on 流动 + https://liudon.com/tags/%E5%85%AC%E7%A7%AF%E9%87%91/ + Recent content in 公积金 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 17 Jan 2020 17:14:32 +0800 + + + 如何在北京公积金网站上修改婚姻状况 + https://liudon.com/posts/how-to-modify-marital-status/ + Fri, 17 Jan 2020 17:14:32 +0800 + https://liudon.com/posts/how-to-modify-marital-status/ + <blockquote> +<p>关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告</p> +<p>时间:2020年01月08日</p> +<p>来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html</p> + +

      关于取消住房公积金提取业务纸质申请表及部分业务网上办结的公告

      +

      时间:2020年01月08日

      +

      来源:http://gjj.beijing.gov.cn/web/zwgk/_300583/zxzysx/675803/index.html

      + +

      1月8日,北京公积金中心发文,从1月10日开始可以网上办理公积金提取了。

      +

      这里单独讲一下外地领证的情况下,如何修改婚姻状况。

      +
        +
      1. +

        进入提取页面,默认显示为未说明的婚姻状况。

        +

        + +QQ截图20200117170836.jpg + + +

        +
      2. +
      3. +

        点击婚姻状况,选择已婚。

        +

        + +QQ截图20200117170917.jpg + + +

        +

        可以看到婚姻状态相关的输入框都为灰色,不可修改。

        +
      4. +
      5. +

        点击婚姻信息修改按钮,会弹出一个民政校验的弹窗,因为我是外地领证,这里查不到信息。

        +

        + +QQ截图20200117171149.jpg + + +

        +

        注意图片右下角还是只有一个婚姻信息修改按钮。

        +
      6. +
      7. +

        点击弹窗里的确认按钮。

        +

        + +QQ截图20200117171236.jpg + + +

        +

        这下婚姻状况相关的输入框都可以填写了。

        +

        另外图片右下角里多了一个上传结婚证按钮。

        +
      8. +
      9. +

        填写完信息后,点击上传结婚证按钮。

        +

        + +QQ截图20200117171252.jpg + + +

        +
      10. +
      11. +

        按说明上传两张结婚证照片,点击确认即可。

        +
      12. +
      13. +

        其余的按公积金官网文档操作,最后提交即可。

        +
      14. +
      +

      关于提取时间,我是前一天中午申请,第二天下午就到账了,效率还是很棒的。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\205\254\347\247\257\351\207\221/page/1/index.html" "b/tags/\345\205\254\347\247\257\351\207\221/page/1/index.html" new file mode 100644 index 000000000..5d3825d05 --- /dev/null +++ "b/tags/\345\205\254\347\247\257\351\207\221/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%85%AC%E7%A7%AF%E9%87%91/ + \ No newline at end of file diff --git "a/tags/\345\214\227\344\272\254/index.html" "b/tags/\345\214\227\344\272\254/index.html" new file mode 100644 index 000000000..7a8304c8f --- /dev/null +++ "b/tags/\345\214\227\344\272\254/index.html" @@ -0,0 +1,8 @@ +北京 | 流动 +

      疫情下的生活

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。 +昨天看新闻,基本没有社会面新增了,感觉要解封了。 +没想到今天直接被打脸,封控升级了。 +...

      2022-05-20 · 1 min · 189 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\214\227\344\272\254/index.xml" "b/tags/\345\214\227\344\272\254/index.xml" new file mode 100644 index 000000000..9a2445939 --- /dev/null +++ "b/tags/\345\214\227\344\272\254/index.xml" @@ -0,0 +1,48 @@ + + + + 北京 on 流动 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC/ + Recent content in 北京 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 20 May 2022 19:17:57 +0800 + + + 疫情下的生活 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220520-192500@2x.png" width="1442" height="924" alt="20220520-192500@2x" title="" loading="lazy" /> + </picture> + +</p> +<p>不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。</p> +<p>昨天看新闻,基本没有社会面新增了,感觉要解封了。</p> +<p>没想到今天直接被打脸,封控升级了。</p> + + +20220520-192500@2x + + +

      +

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。

      +

      昨天看新闻,基本没有社会面新增了,感觉要解封了。

      +

      没想到今天直接被打脸,封控升级了。

      +

      居家办公已经快两周了,也不知道这种日子还要多久。

      +

      在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。

      +

      媳妇帮我想了个办法,投屏到电视上。

      +

      + +20220520194127 + + +

      +

      居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。

      +

      不知道这波还要多久。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\214\227\344\272\254/page/1/index.html" "b/tags/\345\214\227\344\272\254/page/1/index.html" new file mode 100644 index 000000000..e410e2970 --- /dev/null +++ "b/tags/\345\214\227\344\272\254/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%8C%97%E4%BA%AC/ + \ No newline at end of file diff --git "a/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.html" "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.html" new file mode 100644 index 000000000..577ad4995 --- /dev/null +++ "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.html" @@ -0,0 +1,7 @@ +北京幼升小 | 流动 +

      记录2022年海淀幼升小

      18年的热点新闻,纳税千万孩子无法在北京上学。 +一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。 +...

      2022-05-25 · 2 min · 715 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.xml" "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.xml" new file mode 100644 index 000000000..6dcdda66f --- /dev/null +++ "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/index.xml" @@ -0,0 +1,129 @@ + + + + 北京幼升小 on 流动 + https://liudon.com/tags/%E5%8C%97%E4%BA%AC%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Recent content in 北京幼升小 on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 25 May 2022 20:10:01 +0800 + + + 记录2022年海淀幼升小 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220525202612.png" width="1920" height="2243" alt="20220525202612" title="" loading="lazy" /> + </picture> + +</p> +<blockquote> +<p>18年的热点新闻,纳税千万孩子无法在北京上学。</p> +</blockquote> +<p>一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。</p> + + +20220525202612 + + +

      +
      +

      18年的热点新闻,纳税千万孩子无法在北京上学。

      +
      +

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      +

      提前在网上搜了一番资料,都是一些机构整理的信息。

      +

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      +

      1. 信息采集

      +

      5月5日,采集系统开放。

      +

      当天下午录入相关信息,提交网上审核。

      +

      2. 网上审核

      +

      信息提交后,就开始了漫长的审核时间。

      +
      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      + +20220527202411 + + +

      +

      3. 线下审核

      +

      网上审核通过后,打印入学申请表,预约线下审核时间。

      +

      + +20220527202458 + + +

      +

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      +

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      +

      后来交流一番后,发现是自己理解错了。

      +

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      +
      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      + +街道审核 + + +

      +
        +
      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • +
      • 工作证明还需要提供满足时间要求的社保缴费记录。
      • +
      +

      4. 审核通过

      +

      5月27日,审核通过后,打印信息采集表。

      +

      + +20220527202545 + + +

      +

      5. 学校登记

      +

      6月1日对口学校发布入学登记通知书。

      +

      按通知书准备资料,到登记时间去学校交资料。

      +

      今年遇到疫情,改为线上邮件发送资料登记了。

      +

      6. 填报志愿

      +

      6月23日,海淀教育发文1911后填报志愿通知

      +

      + +20220629215840 + + +

      +

      第一志愿锁定,其他志愿自己选择填报。

      +

      6月25日锁定,不允许再修改。

      +
      +

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      +

      租房的不需要填报志愿,等待派位。

      +
      +

      7. 查看结果

      +

      + +20220629220638 + + +

      +

      6月29日15点,系统开放结果查询。

      +

      第一志愿录取,一直担心的调剂没有发生。

      +

      7月10日,收到教委短信,系统查询录取通知书。

      +

      + +20220710085749 + + +

      +

      + +20220710084342 + + +

      +

      历时1个多月的幼升小总算落地了。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/page/1/index.html" "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/page/1/index.html" new file mode 100644 index 000000000..f3990f278 --- /dev/null +++ "b/tags/\345\214\227\344\272\254\345\271\274\345\215\207\345\260\217/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%8C%97%E4%BA%AC%E5%B9%BC%E5%8D%87%E5%B0%8F/ + \ No newline at end of file diff --git "a/tags/\345\215\232\345\256\242/index.html" "b/tags/\345\215\232\345\256\242/index.html" new file mode 100644 index 000000000..f0eaf85e5 --- /dev/null +++ "b/tags/\345\215\232\345\256\242/index.html" @@ -0,0 +1,7 @@ +博客 | 流动 +

      整理下博客的一些调整

      新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。 +更新主题版本,展示文章tag标签 通过对比主题作者的网站,发现使用的不是最新代码。 +...

      2022-05-13 · 1 min · 331 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\215\232\345\256\242/index.xml" "b/tags/\345\215\232\345\256\242/index.xml" new file mode 100644 index 000000000..fb6329976 --- /dev/null +++ "b/tags/\345\215\232\345\256\242/index.xml" @@ -0,0 +1,48 @@ + + + + 博客 on 流动 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2/ + Recent content in 博客 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 13 May 2022 18:20:52 +0800 + + + 整理下博客的一些调整 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + <p>新域名上线一段时间了,通过<code>Google Search Console</code>发现了一些问题,整理下最近进行的一些调整。</p> +<ol> +<li> +<p>更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。</p> + 新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。

      +
        +
      1. +

        更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。

        +

        通过调整Github Actions命令解决:

        +
        - name: Checkout repository
        +    uses: actions/checkout@v2
        +  - name: Checkout submodules
        +    run: git submodule update --init --recursive --remote
        +
      2. +
      3. +

        修正404页面不生效的问题 +主题是自带了404.html文件的,但是部署后没有生成对应文件。

        +

        修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。

        +

        可参考文件代码

        +
      4. +
      5. +

        两个域名导致的页面权重问题 +发现有些页面liudon.xyz收录后,liudon.com就不再收录。

        +

        为了规避这种收录问题,将liudon.xyz直接301到了liudon.com上。

        +
      6. +
      +

      目前已调整完毕,观察后续收录情况。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\215\232\345\256\242/page/1/index.html" "b/tags/\345\215\232\345\256\242/page/1/index.html" new file mode 100644 index 000000000..5daf6ab47 --- /dev/null +++ "b/tags/\345\215\232\345\256\242/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%8D%9A%E5%AE%A2/ + \ No newline at end of file diff --git "a/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.html" "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.html" new file mode 100644 index 000000000..24f1cf6a4 --- /dev/null +++ "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.html" @@ -0,0 +1,21 @@ +博客优化 | 流动 +

      加速Cloudflare访问

      背景 这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。 +众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。 +...

      2024-02-21 · 3 min · 1128 words · Liudon

      使用Hugo实现响应式和优化的图片

      继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。 +问题 在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高。 +经过一段运行后,发现这里有一个弊端。 +Run hugo --gc --minify --cleanDestinationDir Start building sites … hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer 随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。 +...

      2023-12-10 · 5 min · 2021 words · Liudon

      加速Google Analytics

      起因 Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。 +最近在优化页面访问速度,发现Google Analytics是一个优化点。 +优化 1. 访问加速 国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。 +...

      2023-12-02 · 2 min · 870 words · Liudon
      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      累计布局偏移修复方案改进 —— 自动生成图片宽高

      本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片 +遗留的问题 上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。 +手动输入每张图片的宽高 +这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。 +...

      2022-08-24 · 3 min · 1157 words · Liudon

      优化博客的累计布局偏移(CLS)问题

      此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高. +问题表现 7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。 +偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。 +...

      2022-08-20 · 2 min · 886 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.xml" "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.xml" new file mode 100644 index 000000000..d9bff52a7 --- /dev/null +++ "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/index.xml" @@ -0,0 +1,793 @@ + + + + 博客优化 on 流动 + https://liudon.com/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/ + Recent content in 博客优化 on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 21 Feb 2024 20:25:49 +0800 + + + 加速Cloudflare访问 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + Wed, 21 Feb 2024 20:25:49 +0800 + https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/ + <h4 id="背景">背景</h4> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu14843736785607511932.webp 828w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/blog_hu16306086537580768154.png 828w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="blog.png" width="828" height="753" alt="博客架构" title="" loading="lazy" /> + </picture> + +</p> +<p>这是当前的博客架构,文件保存在<code>Github</code>仓库,通过<code>Cloudflare Page</code>提供访问。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10826307968286423618.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E5%8A%A0%E9%80%9Fcloudflare%E8%AE%BF%E9%97%AE/slow_hu10803827016559902140.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="slow.png" width="1842" height="531" alt="国内访问情况" title="" loading="lazy" /> + </picture> + +</p> +<p>众所周知,在国内,<code>Cloudflare</code>的CDN属于反向加速,平均耗时在1.5s左右。</p> + 背景 +

      + +博客架构 + + +

      +

      这是当前的博客架构,文件保存在Github仓库,通过Cloudflare Page提供访问。

      +

      + +国内访问情况 + + +

      +

      众所周知,在国内,Cloudflare的CDN属于反向加速,平均耗时在1.5s左右。

      +

      今天,我们就来讲一下,如何实现国内海外双线路博客访问。

      +

      大体思路

      +
      海外继续走Cloudflare Page,国内再套一层CDN回源Cloudflare Page。
      +
      +Cloudflare Page已经提供了一个cname域名A,形如xxx.pages.dev。
      +
      +国内CDN添加域名后,也会提供一个cname域名B。
      +
      +使用国内dns解析服务,配置cname双线路解析。
      +

      具体操作

      +
        +
      1. +

        Cloudflare Page添加新域名解析

        +

        这个域名是为了给国内CDN回源使用,与博客当前域名不同即可。

        +

        + +Cloudflare Page添加域名 + + +

        +
      2. +
      3. +

        配置国内CDN

        +

        我用的腾讯云,其他服务商也是可以的。

        +

        + +添加CDN域名 + + +

        +
        加速域名:填写博客对外访问的域名
        +回源地址和Host:填写第一步新加的域名
        +

        添加成功后,会有一个cname地址,这里是国内线路解析要用到的。

        +
      4. +
      5. +

        DNS解析调整

        +

        Cloudflare不支持双线路配置,国内服务商支持,我这里用的是腾讯云。

        +

        首先将域名的NS解析改为国内服务商的NS地址,修改后2周左右会收到Cloudflare的邮件,不需要理会。

        +
        liudon.com 的名称服务器不再指向 Cloudflare。它们现在指向:
        +
        +sandals.dnspod.net
        +heron.dnspod.net
        +[not set]
        +[not set]
        +[not set]
        +
        +此更改意味着 liudon.com 不再使用 Cloudflare,因此不会再享受我们的安全和性能服务带来的优势。您的 DNS 记录将在 7 天内从我们的系统中彻底删除。
        +

        然后添加解析,默认走国内CDN,境外走Cloudflare Page

        +

        + +DNS双线路解析 + + +

        +
      6. +
      +

      额外的问题

      +

      为了加速Google Analytics,使用Cloudflare Worker进行了反代,具体见加速Google Analytics

      +

      更改NS后,导致海外访问无法触发Cloudflare Worker了,导致没有博客统计数据了。

      +

      经过一番搜索后,发现Cloudflare Page有类似的Function功能,只需要在网站根目录下新建functions目录,添加对应文件即可。

      +

      这里以Hugo静态博客举例说明:

      +

      在根目录的static目录下,新建functions目录,新建analytics目录,添加post.js文件。

      +

      这个analytics/post.js是为了对应原有Worker的访问地址analytics/post,可自行修改。

      +

      post.js文件代码如下:

      +
      export async function onRequest(context) {
      +    try {
      +        return await postHandler(context);
      +    } catch(e) {
      +        return new Response(`${e.message}\n${e.stack}`, { status: 500 }); 
      +    }
      +}
      +
      +async function postHandler(context) {
      +    const GA_DOMAIN = 'google-analytics.com';
      +    const GA_COLLECT_PATH = 'g\/collect';
      +    const COLLECT_PATH = 'analytics/post';
      +    const DOMAIN = '这里填你博客的域名';
      +
      +    const url = context.request.url;
      +    const cf_ip = context.request.headers.get('CF-Connecting-IP');
      +    const cf_country = context.request.cf.country;
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `${GA_DOMAIN}/${GA_COLLECT_PATH}`)
      +    const newReq = await readRequest(context.request, ga_url);
      +    context.waitUntil(fetch(newReq));
      +
      +    return new Response(null, {
      +        status: 204,
      +        statusText: 'No Content',
      +      });
      +}
      +
      +async function readRequest(request, url) {
      +    const { _, headers } = request;
      +    const nq = {
      +      method: request.method,
      +      headers: {
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: request.body,
      +    };
      +    return new Request(url, nq);
      +}
      +

      优化效果

      +

      + +优化后的访问 + + +

      +

      有了国内CDN的加持,平均耗时优化到1s左右了。

      +]]>
      +
      + + 使用Hugo实现响应式和优化的图片 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + Sun, 10 Dec 2023 08:29:05 +0800 + https://liudon.com/posts/responsive-and-optimized-images-with-hugo/ + <p>继续我们的<a href="../../tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/">博客优化之旅</a>,本篇内容我们将介绍如何使用<code>Hugo</code>实现响应式和优化的图片。</p> +<h4 id="问题">问题</h4> +<p>在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>。</p> +<p>经过一段运行后,发现这里有一个弊端。</p> +<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Run hugo --gc --minify --cleanDestinationDir +</span></span><span style="display:flex;"><span>Start building sites … +</span></span><span style="display:flex;"><span>hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 1; STREAM_CLOSED; received from peer +</span></span><span style="display:flex;"><span>ERROR Failed to get JSON resource &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: Get &#34;https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&amp;t=1698674110&#34;: stream error: stream ID 3; STREAM_CLOSED; received from peer +</span></span></code></pre></div><p>随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。</p> + 继续我们的博客优化之旅,本篇内容我们将介绍如何使用Hugo实现响应式和优化的图片。

      +

      问题

      +

      在之前文章里,通过腾讯云数据万象实现了图片优化能力,具体的可参考文章累计布局偏移修复方案改进 —— 自动生成图片宽高

      +

      经过一段运行后,发现这里有一个弊端。

      +
      Run hugo --gc --minify --cleanDestinationDir
      +Start building sites … 
      +hugo v0.119.0-b84644c008e0dc2c4b67bd69cccf87a41a03937e linux/amd64 BuildDate=2023-09-24T15:20:17Z VendorInfo=gohugoio
      +
      +ERROR Failed to get JSON resource "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": Get "https://static.***.com/64412246-9050f100-d0c1-11e9-893a-f9b0766533ad.png?imageInfo&t=1698674110": stream error: stream ID 1; STREAM_CLOSED; received from peer
      +ERROR Failed to get JSON resource "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": Get "https://static.***.com/SkRx5uFwQ8Cliyq.jpg?imageInfo&t=1698674110": stream error: stream ID 3; STREAM_CLOSED; received from peer
      +

      随着图片数量增多,因为需要调接口查询图片信息,这里构建耗时变长,同时也特别容易出现超时导致构建失败。

      +

      失败的时候,需要手动重跑构建,自动化发布卡壳了。

      +

      优化

      +

      经过一番搜索,发现其实Hugo本身是支持图片处理能力的。

      +
      +

      Image processing

      +

      Resize, crop, rotate, filter, and convert images.

      +

      https://gohugo.io/content-management/image-processing/

      +
      +

      下面以我使用的PaperMod主题为例,讲下如何通过image processing实现图片响应式优化。

      +

      image processing需要用到Page bundles,所以文章目录结构需要调整, +将一篇文章的资源(md文件,图片等)放在一个目录下。

      +
      content/
      +├── posts
      +│   ├── my-post
      +│   │   ├── content1.md
      +│   │   ├── content2.md
      +│   │   ├── image1.jpg
      +│   │   ├── image2.png
      +│   │   └── index.md
      +│   └── my-other-post
      +│       └── index.md
      +

      目录结构调整完毕后,接下来修改图片显示文件代码。

      +

      这里需要生成webp格式图片,所以需要使用hugo的extended版本

      +

      PagerMod主题涉及到图片显示的一共三个文件:

      +
        +
      • +

        _default/_markup/render-image.html,对应markdown图片语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}} /*生成的图片规格*/
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +{{ with $src := .Page.Resources.GetMatch .Destination }}
        +    {{- if $responsiveImages -}}
        +        <picture>
        +            /*只有使用了hugo扩展版本的,才生成webp格式图片*/
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +            <img src="{{ $Destination | safeURL }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +        </picture>
        +    {{- else }}
        +        <img src="{{ $Destination | safeURL }}" width="{{ $src.Width }}" height="{{ $src.Height }}" alt="{{ $Text }}" title="{{ $Title }}" loading="lazy" />
        +    {{- end }}
        +{{ end }}
        +
      • +
      • +

        partials/cover.html,对应文章封面解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    /*封面响应式图片配置开关,默认为true*/
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- if $responsiveImages -}}
        +        <picture>
        +        {{- if and hugo.IsExtended (ne $cover.MediaType.Type "image/webp") -}}
        +        <source type="image/webp" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +            {{- end -}}
        +        {{- end -}}" sizes="{{ $dataSzes }}" />
        +        {{- end -}}
        +        <source type="{{ $cover.MediaType.Type }}" srcset="
        +        {{- with $respSizes -}}
        +            {{- range $i, $e := . -}}
        +                {{- if ge $cover.Width . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($cover.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}
        +        {{- end -}}, {{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" sizes="{{ $dataSzes }}" />
        +
        +        <img loading="lazy" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        </picture>
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <img loading="lazy" src="{{ (.Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      • +
      • +

        shortcodes/figure.html,对应文章内figure语法解析。

        +
        {{- $respSizes := slice 480 720 1080 -}}
        +{{- $dataSzes := "(min-width: 768px) 720px, 100vw" -}}
        +
        +{{- $holder := "GIP" -}}
        +{{- $hint := "photo" -}}
        +{{- $filter := "box" -}}
        +
        +{{ $src := .Get "src" }}
        +{{ $align := .Get "align" }}
        +{{ $alt := .Get "alt" }}
        +{{ $caption := .Get "caption" }}
        +
        +/*内容图片响应式开关配置,默认为true*/
        +{{- $responsiveImages := (.Page.Params.responsiveImages | default site.Params.responsiveImages) | default true }}
        +
        +<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
        +        {{- if eq (.Get "align") "center" }}align-center {{ end }}
        +        {{- with .Get "class" }}{{ . }}{{- end }}"
        +{{- end -}}>
        +    {{- if .Get "link" -}}
        +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
        +    {{- end }}
        +    {{ with $src := $.Page.Resources.GetMatch (.Get "src") }}
        +    <picture>
        +        {{- if $responsiveImages -}}
        +            {{- if and hugo.IsExtended (ne $src.MediaType.Type "image/webp") -}}
        +            <source type="image/webp" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x webp " $hint " " $filter) ).RelPermalink | absURL }} {{ . }}w
        +                {{- end -}}
        +            {{- end -}}" sizes="{{ $dataSzes }}" />
        +            {{- end -}}
        +            <source type="{{ $src.MediaType.Type }}" srcset="
        +            {{- with $respSizes -}}
        +                {{- range $i, $e := . -}}
        +                    {{- if ge $src.Width . -}}
        +                        {{- if $i }}, {{ end -}}{{- ($src.Resize (print . "x jpg " $filter) ).RelPermalink | absURL}} {{ . }}w
        +                    {{- end -}}
        +                {{- end -}}
        +            {{- end -}}, {{$src.Permalink }} {{printf "%dw" ($src.Width)}}" sizes="{{ $dataSzes }}" />
        +        {{- end }}
        +        <img loading="lazy" src="{{ $src }}{{- if eq ($align) "center" }}#center{{- end }}"
        +        {{- if or ($alt) ($caption) }}
        +        alt="{{ with $alt }}{{ . }}{{ else }}{{ $caption | markdownify| plainify }}{{ end }}"
        +        {{- end -}}
        +        {{- with $src.Width -}} width="{{ . }}"{{- end -}}
        +        {{- with $src.Height -}} height="{{ . }}"{{- end -}}
        +        /> <!-- Closing img tag -->
        +    </picture>
        +    {{ end }}
        +    {{- if .Get "link" }}</a>{{ end -}}
        +    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        +        <figcaption>
        +            {{ with (.Get "title") -}}
        +                {{ . }}
        +            {{- end -}}
        +            {{- if or (.Get "caption") (.Get "attr") -}}<p>
        +                {{- .Get "caption" | markdownify -}}
        +                {{- with .Get "attrlink" }}
        +                    <a href="{{ . }}">
        +                {{- end -}}
        +                {{- .Get "attr" | markdownify -}}
        +                {{- if .Get "attrlink" }}</a>{{ end }}</p>
        +            {{- end }}
        +        </figcaption>
        +    {{- end }}
        +</figure>
        +
      • +
      +

      使用效果

      +

      正常插入jpg/png图片,构建后会自动生成webp/原始格式下不同规格的图片。

      +
        +
      • markdown图片显示
      • +
      +

      + +render-image + + +

      +
        +
      • figure短代码显示
      • +
      +

      + +figure + + +

      +
        +
      • 封面显示
      • +
      +

      + +cover + + +

      +

      提示:

      +

      随着图片数量增多,可能会遇到构建超时的错误,类似下述信息:

      +
      Start building sites  
      +hugo v0.96.0+extended darwin/arm64 BuildDate=unknown
      +Error: Error building site: "/Users/dondonliu/Code/liudon.github.io/content/posts/xxxx/index.md:1:1": timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.
      +Built in 22356 ms
      +

      可以通过修改配置文件config.yml,新增timeout配置,调大超时时间解决。

      +
      buildDrafts: false
      +buildFuture: false
      +buildExpired: false
      +
      +timeout: 60s // 调大此处的时间即可
      +

      终于知道为啥PagerMod主题默认只有封面下才有生成不同规格的逻辑了。

      +]]>
      +
      + + 加速Google Analytics + https://liudon.com/posts/optimize-google-analytics/ + Sat, 02 Dec 2023 09:25:49 +0800 + https://liudon.com/posts/optimize-google-analytics/ + <h3 id="起因">起因</h3> +<p><code>Google Analytics</code>是一款优秀的流量分析服务,集成方便,使用简单。</p> +<p>最近在优化页面访问速度,发现<code>Google Analytics</code>是一个优化点。</p> +<h3 id="优化">优化</h3> +<h4 id="1-访问加速">1. 访问加速</h4> +<p>国内访问<code>Google Analytics</code>很慢,同时还面临着各种广告屏蔽插件拦截。</p> + 起因 +

      Google Analytics是一款优秀的流量分析服务,集成方便,使用简单。

      +

      最近在优化页面访问速度,发现Google Analytics是一个优化点。

      +

      优化

      +

      1. 访问加速

      +

      国内访问Google Analytics很慢,同时还面临着各种广告屏蔽插件拦截。

      +

      这里借助Cloudflare Worker实现Google Analytics反代,同时更换采集路由规避广告屏蔽插件的拦截。

      +

      Cloudflare新建Worker,代码如下,保存后部署。

      +
      addEventListener('fetch', (event) => {
      +    // 这里可以加 filter
      +    return event.respondWith(handleRequest(event));
      +  });
      +  
      +  // worker 应用的路由地址,末尾不加 '/',改为你的博客地址
      +  const DOMAIN = 'xxx.com';
      +  // 博客插入的js地址文件名,可自定义
      +  const JS_FILE = 'ga.js'
      +  // 响应上报的接口路径,可自定义,规避广告屏蔽插件拦截
      +  const COLLECT_PATH = 'collect_path';
      +  //  gtag 地址,将G-XXX改为你的id
      +  const JS_URL = 'https://www.googletagmanager.com/gtag/js?id=G-XXX'
      +  
      +  // 下面不需要改
      +  const G_DOMAIN = 'google-analytics.com';
      +  const G_COLLECT_PATH = 'g\/collect';
      +  
      +  async function handleRequest(event) {
      +    const url = event.request.url;
      +    if (url.match(`${DOMAIN}/${JS_FILE}`)) {
      +      const requestJs = await (await fetch(JS_URL)).text();
      +      const jsText = requestJs.replaceAll('\"www\"', '\"\"').replaceAll('.' + G_DOMAIN, DOMAIN).replaceAll(G_COLLECT_PATH, COLLECT_PATH);
      +  
      +      return new Response(jsText, {
      +        status: 200,
      +        statusText: 'OK',
      +        headers: {
      +          'Content-Type': 'application/javascript',
      +        },
      +      });
      +    } else if (url.match(`${DOMAIN}/${COLLECT_PATH}`)) {
      +        const newReq = await readRequest(event.request);
      +        event.waitUntil(fetch(newReq));
      +    }
      +    return new Response(null, {
      +      status: 204,
      +      statusText: 'No Content',
      +    });
      +  }
      +  
      +  async function readRequest(request) {
      +    const { url, headers } = request;
      +    const body = await request.text();
      +    const ga_url = url.replace(`${DOMAIN}/${COLLECT_PATH}`, `www.${G_DOMAIN}/${G_COLLECT_PATH}`);
      +    const nq = {
      +      method: 'POST',
      +      headers: {
      +        Host: 'www.google-analytics.com',
      +        Origin: headers.get('origin'),
      +        'Cache-Control': 'max-age=0',
      +        'User-Agent': headers.get('user-agent'),
      +        Accept: headers.get('accept'),
      +        'Accept-Language': headers.get('accept-language'),
      +        'Content-Type': headers.get('content-type') || 'text/plain',
      +        Referer: headers.get('referer'),
      +      },
      +      body: body,
      +    };
      +    return new Request(ga_url, nq);
      +  }
      +

      页面插入对应js

      +
      <!-- Google tag (gtag.js) -->
      +<script async src="https://xxx.com/ga.js"></script> // 对应worker里的DOMAIN 和 JS_FILE,需要保持一致
      +<script>
      +  window.dataLayer = window.dataLayer || [];
      +  function gtag(){dataLayer.push(arguments);}
      +  gtag('js', new Date());
      +
      +  gtag('config', 'G-XXX'); // 将G-XXX改为你的id
      +</script>
      +

      2. 文件瘦身

      +

      通过性能分析,发现js文件过大,影响页面加载速度。

      +

      虽然使用了Cloudfare代理,但是Google Analytics原始的js文件为80KB左右。

      +

      搜索一番,找到一个瘦身版Google Analytics

      +
      Minimal Google Analytics Snippet
      +A simple snippet for tracking page views on your website without having to add external libraries. Also works for single page applications made with the likes of react and vue.js.
      +
      +Before
      +Google Tag Manager + Analytics = 73kB
      +
      +After
      +Snippet = 1.5kB
      +

      插入的js片断,只有1.5kb大小。

      +

      唯一的缺点就是只有基本功能,这对于我们来说足够了。

      +

      将第一步插入的js代码,更换为下述代码,需要将其中的xxx.com/collect_path改为第一步定义的DOMAIN和COLLECT_PATH变量值。

      +
      <script>
      +enScroll=!1,enFdl=!1,extCurrent=void 0,filename=void 0,targetText=void 0,splitOrigin=void 0;const lStor=localStorage,sStor=sessionStorage,doc=document,docEl=document.documentElement,docBody=document.body,docLoc=document.location,w=window,s=screen,nav=navigator||{},extensions=["pdf","xls","xlsx","doc","docx","txt","rtf","csv","exe","key","pps","ppt","pptx","7z","pkg","rar","gz","zip","avi","mov","mp4","mpe","mpeg","wmv","mid","midi","mp3","wav","wma"];function a(e,t,n,o){const j="G-G9ZDJQN9E2",r=()=>Math.floor(Math.random()*1e9)+1,c=()=>Math.floor(Date.now()/1e3),F=()=>(sStor._p||(sStor._p=r()),sStor._p),E=()=>r()+"."+c(),_=()=>(lStor.cid_v4||(lStor.cid_v4=E()),lStor.cid_v4),m=lStor.getItem("cid_v4"),v=()=>m?void 0:enScroll==!0?void 0:"1",p=()=>(sStor.sid||(sStor.sid=c()),sStor.sid),O=()=>{if(!sStor._ss)return sStor._ss="1",sStor._ss;if(sStor.getItem("_ss")=="1")return void 0},a="1",g=()=>{if(sStor.sct)if(enScroll==!0)return sStor.sct;else x=+sStor.getItem("sct")+ +a,sStor.sct=x;else sStor.sct=a;return sStor.sct},i=docLoc.search,b=new URLSearchParams(i),h=["q","s","search","query","keyword"],y=h.some(e=>i.includes("&"+e+"=")||i.includes("?"+e+"=")),u=()=>y==!0?"view_search_results":enScroll==!0?"scroll":enFdl==!0?"file_download":"page_view",f=()=>enScroll==!0?"90":void 0,C=()=>{if(u()=="view_search_results"){for(let e of b)if(h.includes(e[0]))return e[1]}else return void 0},d=encodeURIComponent,k=e=>{let t=[];for(let n in e)e.hasOwnProperty(n)&&e[n]!==void 0&&t.push(d(n)+"="+d(e[n]));return t.join("&")},A=!1,S="https://xxx.com/collect_path",M=k({v:"2",tid:j,_p:F(),sr:(s.width*w.devicePixelRatio+"x"+s.height*w.devicePixelRatio).toString(),ul:(nav.language||void 0).toLowerCase(),cid:_(),_fv:v(),_s:"1",dl:docLoc.origin+docLoc.pathname+i,dt:doc.title||void 0,dr:doc.referrer||void 0,sid:p(),sct:g(),seg:"1",en:u(),"epn.percent_scrolled":f(),"ep.search_term":C(),"ep.file_extension":e||void 0,"ep.file_name":t||void 0,"ep.link_text":n||void 0,"ep.link_url":o||void 0,_ss:O(),_dbg:A?1:void 0}),l=S+"?"+M;if(nav.sendBeacon)nav.sendBeacon(l);else{let e=new XMLHttpRequest;e.open("POST",l,!0)}}a();function sPr(){return(docEl.scrollTop||docBody.scrollTop)/((docEl.scrollHeight||docBody.scrollHeight)-docEl.clientHeight)*100}doc.addEventListener("scroll",sEv,{passive:!0});function sEv(){const e=sPr();if(e<90)return;enScroll=!0,a(),doc.removeEventListener("scroll",sEv,{passive:!0}),enScroll=!1}document.addEventListener("DOMContentLoaded",function(){let e=document.getElementsByTagName("a");for(let t=0;t<e.length;t++)if(e[t].getAttribute("href")!=null){const n=e[t].getAttribute("href"),s=n.substring(n.lastIndexOf("/")+1),o=s.split(".").pop();(e[t].hasAttribute("download")||extensions.includes(o))&&e[t].addEventListener("click",fDl,{passive:!0})}});function fDl(e){enFdl=!0;const t=e.currentTarget.getAttribute("href"),n=t.substring(t.lastIndexOf("/")+1),s=n.split(".").pop(),o=n.replace("."+s,""),i=e.currentTarget.text,r=t.replace(docLoc.origin,"");a(s,o,i,r),enFdl=!1}
      +</script>
      +

      效果

      +

      + + + + +

      +]]>
      +
      + + 累计布局偏移修复方案改进 —— 自动生成图片宽高 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + Wed, 24 Aug 2022 12:37:22 +0800 + https://liudon.com/posts/hugo-auto-generate-image-width-and-height/ + <p>本站已不再采用本方案,新方案见<a href="../../posts/responsive-and-optimized-images-with-hugo/">使用Hugo实现响应式和优化的图片</a></p> +<h4 id="遗留的问题">遗留的问题</h4> +<p>上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。</p> +<blockquote> +<p>手动输入每张图片的宽高</p> +</blockquote> +<p>这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。</p> + 本站已不再采用本方案,新方案见使用Hugo实现响应式和优化的图片

      +

      遗留的问题

      +

      上一篇文章讲了我是如何解决博客累计布局偏移的问题,但是这个方案存在一个很大的问题。

      +
      +

      手动输入每张图片的宽高

      +
      +

      这就要求每次插入图片后,需要手动查看图片宽高,修改插入代码,导致整个流程变得繁琐,无法自动化。

      +

      身为一名工程师,对于这样一个痛点,势必要优化掉。

      +

      思路

      +

      发完上一篇文章后,我一直在想怎么能实现自动化插入图片宽高。

      +

      要插入的图片代码是类似这样的:

      +
      {{< figure src="https://static.liudon.com/img/cover-code.png" alt="cover.html code" width="2020" height="1468" >}}
      +

      我使用了picgo插件,上传图片到腾讯云对象存储,然后复制markdown图片代码插入文章。

      +

      能不能通过改造picgo插件,将上传后复制的代码,加上图片的宽高参数?

      +

      通过一番搜索,发现此方案不通,picgo确实支持自定义代码,但是变量仅支持文件名url

      +

      + +picgo config + + +

      +

      此路不通,只好再想新的办法了。

      +

      对象存储的图片处理有接口,可以返回图片的宽高信息,详细说明见获取图片基本信息

      +

      能不能在生成文件的时候,通过发起一个请求拿到图片宽高,然后写入html代码?

      +

      经过一番搜索,发现hugo支持请求url,详细说明见Get Remote Data

      +
      {{ $dataJ := getJSON "url" }}
      +{{ $dataC := getCSV "separator" "url" }}
      +

      哈哈,柳暗花明又一村的感觉。

      +

      解决方案

      +

      此方案基于对象存储获取图片宽高,然后写入图片解析模板。

      +
        +
      1. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      2. +
      3. +

        添加render-image.html文件

        +

        代码如下:

        +
        {{- $item := getJSON .Destination "?imageInfo&t=" now.Unix -}}
        +{{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +
        +{{- $Destination := .Destination -}}
        +{{- $Text := .Text -}}
        +{{- $Title := .Title -}}
        +
        +{{- with $item }}
        +<picture>
        +    <source type="image/webp" srcset="{{ $Destination | safeURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +    <img loading="lazy" src="{{ $Destination | safeURL }}" alt="{{ $Text }}" {{ with $Title}} title="{{ . }}" {{ end }} width="{{ .width }}" height="{{ .height }}" />
        +</picture>
        +{{- end -}}
        +
      4. +
      5. +

        添加cover.html文件

        +

        代码如下:

        +
        {{- with .cxt}} {{/* Apply proper context from dict */}}
        +{{- if (and .Params.cover.image (not $.isHidden)) }}
        +{{- $alt := (.Params.cover.alt | default .Params.cover.caption | plainify) }}
        +<figure class="entry-cover">
        +    {{- $responsiveImages := (.Params.cover.responsiveImages | default site.Params.cover.responsiveImages) | default true }}
        +    {{- $addLink := (and site.Params.cover.linkFullImages (not $.IsHome)) }}
        +    {{- $cover := (.Resources.ByType "image").GetMatch (printf "*%s*" (.Params.cover.image)) }}
        +    {{- if $cover -}}{{/* i.e it is present in page bundle */}}
        +        {{- if $addLink }}<a href="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +        {{- $sizes := (slice "360" "480" "720" "1080" "1500") }}
        +        {{- $processableFormats := (slice "jpg" "jpeg" "png" "tif" "bmp" "gif") -}}
        +        {{- if hugo.IsExtended -}}
        +            {{- $processableFormats = $processableFormats | append "webp" -}}
        +        {{- end -}}
        +        {{- $prod := (hugo.IsProduction | or (eq site.Params.env "production")) }}
        +        {{- if (and (in $processableFormats $cover.MediaType.SubType) ($responsiveImages) (eq $prod true)) }}
        +        <img loading="lazy" srcset="{{- range $size := $sizes -}}
        +                        {{- if (ge $cover.Width $size) -}}
        +                        {{ printf "%s %s" (($cover.Resize (printf "%sx" $size)).Permalink) (printf "%sw ," $size) -}}
        +                        {{ end }}
        +                    {{- end -}}{{$cover.Permalink }} {{printf "%dw" ($cover.Width)}}" 
        +            sizes="(min-width: 768px) 720px, 100vw" src="{{ $cover.Permalink }}" alt="{{ $alt }}" 
        +            width="{{ $cover.Width }}" height="{{ $cover.Height }}">
        +        {{- else }}{{/* Unprocessable image or responsive images disabled */}}
        +        <img loading="lazy" src="{{ (path.Join .RelPermalink .Params.cover.image) | absURL }}" alt="{{ $alt }}">
        +        {{- end }}
        +    {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- $item := getJSON .Params.cover.image "?imageInfo&t=" now.Unix -}}
        +        {{/* 通过对象存储接口获取图片宽高,因为我使用了cdn,所以增加随机数保证拿到最新的图片宽高参数 */}}
        +        {{- $coverUrl := .Params.cover.image -}}
        +        {{- with $item }}
        +        {{- if $addLink }}<a href="{{ $coverUrl | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ $coverUrl | absURL }}/webp" width="{{ .width }}" height="{{ .height }}">
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ $coverUrl | absURL }}" width="{{ .width }}" height="{{ .height }}">
        +            </picture>
        +        {{- end }}
        +    {{- end }}
        +    {{- if $addLink }}</a>{{ end -}}
        +    {{/*  Display Caption  */}}
        +    {{- if not $.IsHome }}
        +        {{ with .Params.cover.caption }}<p>{{ . | markdownify }}</p>{{- end }}
        +    {{- end }}
        +</figure>
        +{{- end }}{{/* End image */}}
        +{{- end -}}{{/* End context */ -}}
        +
      6. +
      +

      使用方式和原来不变,插入markdown语法的图片代码即可。

      +
      ![picgo config](picgo-config.png)
      +

      这下又可以愉快地码字了。

      +]]>
      +
      + + 优化博客的累计布局偏移(CLS)问题 + https://liudon.com/posts/fix-blog-cls/ + Sat, 20 Aug 2022 07:27:22 +0800 + https://liudon.com/posts/fix-blog-cls/ + <p>此文已过期,优化方案参考<a href="https://liudon.com/posts/hugo-auto-generate-image-width-and-height/">累计布局偏移修复方案改进 —— 自动生成图片宽高</a>.</p> +<h4 id="问题表现">问题表现</h4> +<p>7月份将博客部署由<code>Github</code>迁移到<code>Cloudflare</code>后,开始关注博客的性能问题。</p> +<p>偶然看到苏卡卡大佬的<a href="https://blog.skk.moe/post/fix-blog-cls/">CLS优化文章</a>,拿自己博客也测试了下,发现也存在同样的问题。</p> + 此文已过期,优化方案参考累计布局偏移修复方案改进 —— 自动生成图片宽高.

      +

      问题表现

      +

      7月份将博客部署由Github迁移到Cloudflare后,开始关注博客的性能问题。

      +

      偶然看到苏卡卡大佬的CLS优化文章,拿自己博客也测试了下,发现也存在同样的问题。

      +

      + +Lighthouse测试报告 + + +

      +

      根据苏卡卡大佬的文章,分析页面是由于文章封面的图片缺少宽高导致出现CLS问题。

      +

      为了解决这个问题,需要指定封面的宽高参数。

      +

      + +cover.html code + + +

      +

      根据PagerMod主题的cover.html文件代码,使用绝对地址的情况没有配置宽高参数。

      +

      解决方案

      +
        +
      1. +

        新增封面配置

        +

        文章封面配置新增widthheight属性。

        +
        cover:
        +    image: "https://static.liudon.com/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220725183817.jpg"
        +    width: 1620
        +    height: 1080
        +
      2. +
      3. +

        自定义封面文件

        +

        添加自己的cover.html文件,核心代码如下,完整代码参考我的文件

        +
            {{- else }}{{/* For absolute urls and external links, no img processing here */}}
        +        {{- if $addLink }}<a href="{{ (.Params.cover.image) | absURL }}" target="_blank"
        +            rel="noopener noreferrer">{{ end -}}
        +            <picture>
        +            <source type="image/webp" srcset="{{ (.Params.cover.image) | absURL }}/webp" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            <img loading="lazy" alt="{{ $alt }}" src="{{ (.Params.cover.image) | absURL }}" {{- if (.Params.cover.width) }}width="{{ (.Params.cover.width) }}"{{ end -}} {{- if (.Params.cover.height) }}height="{{ (.Params.cover.height) }}"{{ end -}}>
        +            </picture>
        +    {{- end }}
        +

        img标签新增widthheight属性,读取封面配置的widthheight属性值。

        +

        图片我放到了腾讯云对象存储上,通过图片处理支持了webp格式图片。

        +

        + +cos-img-process + + +

        +
      4. +
      5. +

        新增css配置

        +

        新增如下配置,否则会导致图片变形。

        +
        img {
        +    width:100%;
        +    height:auto;
        +}
        +
        +figure {
        +    background-color: var(--code-bg);
        +}
        +
      6. +
      +

      再进一步

      +

      前面只解决了首页封面,文章页也会存在图片的情况,也会有类似的问题。

      +

      基于markdown语法的图片代码,是不支持宽高参数的。

      +

      还好hugo支持shortcode,其中就有figure语法,支持配置宽高参数。

      +

      + +figure.html code + + +

      +

      我们使用figure语法插入图片,指定图片宽高。

      +

      figure解析模板我也进行了改进,类似cover.html模板,也通过对象存储图片处理支持了webp响应式图片,核心代码如下,完整代码参考我的文件

      +
          {{- if .Get "link" -}}
      +        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
      +    {{- end }}
      +    <picture>
      +        <source type="image/webp" srcset="{{ .Get "src" }}/webp" {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +        {{- with .Get "height" }} height="{{ . }}"{{ end -}}>
      +        <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
      +         {{- if or (.Get "alt") (.Get "caption") }}
      +         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
      +         {{- end -}}
      +         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
      +         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
      +        /> <!-- Closing img tag -->
      +    </picture>
      +    {{- if .Get "link" }}</a>{{ end -}}
      +

      使用方式:

      +
      +

      {{< figure src=“cover-code.png” alt=“cover.html code” width=“2020” height=“1468” >}}

      +
      +

      + +gtmetrix-result + + +

      +

      至此累计布局偏移(CLS)问题解决了,同时也支持了响应式图片。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\215\232\345\256\242\344\274\230\345\214\226/page/1/index.html" "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/page/1/index.html" new file mode 100644 index 000000000..023c9b9d6 --- /dev/null +++ "b/tags/\345\215\232\345\256\242\344\274\230\345\214\226/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%8D%9A%E5%AE%A2%E4%BC%98%E5%8C%96/ + \ No newline at end of file diff --git "a/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.html" "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.html" new file mode 100644 index 000000000..160938f4c --- /dev/null +++ "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.html" @@ -0,0 +1,9 @@ +带着相机晒太阳 | 流动 +
      向日葵

      向日葵

      奥林匹克公园向日葵之旅

      媳妇有事回老家了,这两天自己带娃。 +小区群里有人说奥林匹克公园的向日葵开了,适合拍照。 +正好周六多云,没有太阳,出门遛娃。 +带上我好久不用的相机,省得发霉了。 +...

      2022-07-21 · 1 min · 179 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.xml" "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.xml" new file mode 100644 index 000000000..d126e9595 --- /dev/null +++ "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/index.xml" @@ -0,0 +1,56 @@ + + + + 带着相机晒太阳 on 流动 + https://liudon.com/tags/%E5%B8%A6%E7%9D%80%E7%9B%B8%E6%9C%BA%E6%99%92%E5%A4%AA%E9%98%B3/ + Recent content in 带着相机晒太阳 on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 21 Jul 2022 20:40:20 +0800 + + + 奥林匹克公园向日葵之旅 + https://liudon.com/posts/olympic-park-sunflower-tour/ + Thu, 21 Jul 2022 20:40:20 +0800 + https://liudon.com/posts/olympic-park-sunflower-tour/ + <p>媳妇有事回老家了,这两天自己带娃。</p> +<p>小区群里有人说奥林匹克公园的向日葵开了,适合拍照。</p> +<p>正好周六多云,没有太阳,出门遛娃。</p> +<p>带上我好久不用的相机,省得发霉了。</p> + 媳妇有事回老家了,这两天自己带娃。

      +

      小区群里有人说奥林匹克公园的向日葵开了,适合拍照。

      +

      正好周六多云,没有太阳,出门遛娃。

      +

      带上我好久不用的相机,省得发霉了。

      +

      以往都是去的南园,第一次来北园。

      +

      西门进入,沿着路往右走,一会就到。

      +

      人很多,估计大家都因为之前疫情在家憋疯了。

      +

      到了没多久,太阳又出来了,超级晒。

      +

      提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。

      +

      + +向日葵 + + + + + +向日葵 + + + + + +荷花 + + + + + +荷叶上的蜻蜓 + + +

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/page/1/index.html" "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/page/1/index.html" new file mode 100644 index 000000000..37f52ead9 --- /dev/null +++ "b/tags/\345\270\246\347\235\200\347\233\270\346\234\272\346\231\222\345\244\252\351\230\263/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%B8%A6%E7%9D%80%E7%9B%B8%E6%9C%BA%E6%99%92%E5%A4%AA%E9%98%B3/ + \ No newline at end of file diff --git "a/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" new file mode 100644 index 000000000..0e00c7860 --- /dev/null +++ "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.html" @@ -0,0 +1,11 @@ +年终总结 | 流动 +

      2023年终总结

      2023年过完了,是时候来个总结了。 +博客 2023年一共更新了15篇内容,共计12000字。 +访问Top3的文章: +...

      2024-01-04 · 2 min · 748 words · Liudon

      2022年终总结

      2022年已经过去1周多了,记录一下我的2022年。 +疫情 2022年,是新冠疫情的第三年,也是切身感受到的一年。 +3月22日晚,8点半和同事刚上13号线地铁。 +...

      2023-01-12 · 2 min · 646 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.xml" "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.xml" new file mode 100644 index 000000000..101d2900a --- /dev/null +++ "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/index.xml" @@ -0,0 +1,251 @@ + + + + 年终总结 on 流动 + https://liudon.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + Recent content in 年终总结 on 流动 + Hugo -- 0.134.3 + zh-cn + Thu, 04 Jan 2024 18:41:20 +0800 + + + 2023年终总结 + https://liudon.com/posts/review-2023/ + Thu, 04 Jan 2024 18:41:20 +0800 + https://liudon.com/posts/review-2023/ + <p>2023年过完了,是时候来个总结了。</p> +<h4 id="博客">博客</h4> +<p>2023年一共更新了15篇内容,共计12000字。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/review-2023/20240104184720_hu9917978577706645891.webp 733w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/review-2023/20240104184720_hu6551399417996237227.png 733w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20240104184720.png" width="733" height="436" alt="Google Analytics全年统计" title="" loading="lazy" /> + </picture> + +</p> +<p>访问Top3的文章:</p> + 2023年过完了,是时候来个总结了。

      +

      博客

      +

      2023年一共更新了15篇内容,共计12000字。

      +

      + +Google Analytics全年统计 + + +

      +

      访问Top3的文章:

      +

      将博客部署到星际文件系统(IPFS)

      +

      利用Github Actions定时抓取微博

      +

      优化博客的累计布局偏移(CLS)问题

      +

      主要是因为有在v2ex发帖导流,所以访问量高一些。

      +

      2023年12月北京暴雪记录

      +

      没想到的是一篇暴雪记录,收获了最多的评论,可能大家更容易共情。

      +

      不过从侧面也说明了技术的东西并没有太多人看,所以后来就不再分享导流了。

      +

      工作

      +

      今年搬到了后厂村,见识了互联网的人流。

      +

      在23年最后一个工作日,下班路上,算了一下,这一年晚上9点半之后打车59次。

      +

      而且年底这段时间,打车愈发困难,至少要排队1个小时。

      +

      相比之前一坐坐一天,每天中午会绕公司大楼转两圈。

      +

      + +23年步行统计 + + +

      +

      驾照

      +

      驾照终于到手了

      +

      趁着娃暑假、十一假期,开车上路了。

      +

      + +第一次开车上路 + + +

      +

      参与了第一次摇号。

      +

      + + + + +

      +

      换了新手机

      +

      用了5年的iPhone7 plus,过地铁NFC时不时刷不开了,感觉得换手机了。

      +

      十一假期回来在官网订了15pro,需要11月21日才能发货。

      +

      订货后,老手机的问题越来越多,换手机变的急迫起来。

      +

      于是,开始刷实体店取货。

      +

      用了探火app监控,10月11日中午抢到一台王府井取货。

      +

      10月12日晚上8点出发,上16号线地铁后,老手机开始持续发烫,过一会自动黑屏了。

      +

      按键有触感,但是屏幕不亮,怎么捣鼓也不行,手机越来越烫,都有点怕它炸了。

      +

      换乘8号线的路上,试了试按住所有按键,手机重启了,看见苹果logo那一刻真好。

      +

      重启完总算正常了,进店排队取货,提前了一个月拿到了新手机。

      +

      + +王府井Apple Store + + + + + +取完手机 + + +

      +

      升级体验非常好,使用丝滑,再也不用插着充电器玩手机了,感谢老婆。

      +

      + +大风 + + + + + +公司大楼 + + +

      +

      + +枫叶 + + + + + +秋 + + + + + +冬 + + + + + +冬 + + +

      +

      + +秋天的百望山 + + + + + +冬天的百望山 + + + + + +冬天的百望山 + + +

      +

      财务

      +

      提前还了两笔房贷,希望明年可以把商贷还完。

      +

      股市收益率-1.11%,港股套牢中,美股稍微回了点血。

      +

      + +股市收益 + + +

      +

      本命年了,希望一切顺利。

      +

      祝大家新年快乐!

      +]]>
      +
      + + 2022年终总结 + https://liudon.com/posts/review-2022/ + Thu, 12 Jan 2023 07:21:20 +0800 + https://liudon.com/posts/review-2022/ + <p>2022年已经过去1周多了,记录一下我的2022年。</p> +<h4 id="疫情">疫情</h4> +<p>2022年,是新冠疫情的第三年,也是切身感受到的一年。</p> +<p>3月22日晚,8点半和同事刚上13号线地铁。</p> + 2022年已经过去1周多了,记录一下我的2022年。

      +

      疫情

      +

      2022年,是新冠疫情的第三年,也是切身感受到的一年。

      +

      3月22日晚,8点半和同事刚上13号线地铁。

      +

      紧接着看到群里有人说,公司大厦因为疫情封控了,只进不出。

      +

      + +大厦封控 + + +

      +

      第一次感受弹窗3,居家隔离。

      +

      + +弹窗3 + + +

      +

      + +居家隔离 + + +

      +

      + +居家观察 + + +

      +

      5月21日,开启居家办公。

      +

      6月5日,开始到岗工作。

      +

      11月17日,公司通知第二天居家办公。

      +

      11月21日,小区通知临时管控。

      +

      + +小区封控 + + +

      +

      12月10日,媳妇中招。

      +

      12月12日,自己中招。

      +

      12月18日,娃中招。

      +

      + + +

      +

      12月20日,开始到岗工作,持续了近一个月的居家隔离生活终于结束。

      +

      老妈冒着北京疫情高峰感染的风险,过来帮我们带娃。

      +

      从1周一检,到3天一检,不知道健康宝有没有年终报告,告诉你今年做了多少次核酸,相信会是很棒的一个数字。

      +

      入学

      +

      上半年赶着疫情的间隙,整理好了娃的入学资料。

      +

      经过一个月的焦虑等待后,最终被附近的学校录取,也确实感受到了离家近的好处。

      +

      详细经过见记录2022年海淀幼升小

      +

      休假

      +

      春节没有回家过年,上半年北京和老家交替出现疫情,最终在8月份休假回了趟家。

      +

      驾照

      +

      因为疫情,感觉还是得考个驾照,拖延了N年的事项提上了日程。

      +

      6月份报名,一直拖到10月份才过的科目一。

      +

      11月底开始摸车了,刚上2节课因为疫情封校了,现在学的都快忘光了。

      +

      搬家

      +

      年底公司通知搬家,又搬回了银科,兜兜转转,又回到了起点。

      +

      + +又回银科 + + +

      +

      博客

      +

      2022年,把博客又捡了回来,今年多更新吧。

      +

      + + + + +

      +

      或许是上了年纪,2022年发现泪点变得很低,健康、家人才是最重要的。

      +

      新的一年,继续开源节流,做好防护,保护好家人。

      +

      最后,祝大家新年快乐!

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\271\264\347\273\210\346\200\273\347\273\223/page/1/index.html" "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/page/1/index.html" new file mode 100644 index 000000000..178bb097f --- /dev/null +++ "b/tags/\345\271\264\347\273\210\346\200\273\347\273\223/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ + \ No newline at end of file diff --git "a/tags/\345\271\274\345\215\207\345\260\217/index.html" "b/tags/\345\271\274\345\215\207\345\260\217/index.html" new file mode 100644 index 000000000..0b7bd1683 --- /dev/null +++ "b/tags/\345\271\274\345\215\207\345\260\217/index.html" @@ -0,0 +1,7 @@ +幼升小 | 流动 +

      记录2022年海淀幼升小

      18年的热点新闻,纳税千万孩子无法在北京上学。 +一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。 +...

      2022-05-25 · 2 min · 715 words · Liudon
      \ No newline at end of file diff --git "a/tags/\345\271\274\345\215\207\345\260\217/index.xml" "b/tags/\345\271\274\345\215\207\345\260\217/index.xml" new file mode 100644 index 000000000..6548d9505 --- /dev/null +++ "b/tags/\345\271\274\345\215\207\345\260\217/index.xml" @@ -0,0 +1,129 @@ + + + + 幼升小 on 流动 + https://liudon.com/tags/%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Recent content in 幼升小 on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 25 May 2022 20:10:01 +0800 + + + 记录2022年海淀幼升小 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220525202612.png" width="1920" height="2243" alt="20220525202612" title="" loading="lazy" /> + </picture> + +</p> +<blockquote> +<p>18年的热点新闻,纳税千万孩子无法在北京上学。</p> +</blockquote> +<p>一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。</p> + + +20220525202612 + + +

      +
      +

      18年的热点新闻,纳税千万孩子无法在北京上学。

      +
      +

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      +

      提前在网上搜了一番资料,都是一些机构整理的信息。

      +

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      +

      1. 信息采集

      +

      5月5日,采集系统开放。

      +

      当天下午录入相关信息,提交网上审核。

      +

      2. 网上审核

      +

      信息提交后,就开始了漫长的审核时间。

      +
      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      + +20220527202411 + + +

      +

      3. 线下审核

      +

      网上审核通过后,打印入学申请表,预约线下审核时间。

      +

      + +20220527202458 + + +

      +

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      +

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      +

      后来交流一番后,发现是自己理解错了。

      +

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      +
      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      + +街道审核 + + +

      +
        +
      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • +
      • 工作证明还需要提供满足时间要求的社保缴费记录。
      • +
      +

      4. 审核通过

      +

      5月27日,审核通过后,打印信息采集表。

      +

      + +20220527202545 + + +

      +

      5. 学校登记

      +

      6月1日对口学校发布入学登记通知书。

      +

      按通知书准备资料,到登记时间去学校交资料。

      +

      今年遇到疫情,改为线上邮件发送资料登记了。

      +

      6. 填报志愿

      +

      6月23日,海淀教育发文1911后填报志愿通知

      +

      + +20220629215840 + + +

      +

      第一志愿锁定,其他志愿自己选择填报。

      +

      6月25日锁定,不允许再修改。

      +
      +

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      +

      租房的不需要填报志愿,等待派位。

      +
      +

      7. 查看结果

      +

      + +20220629220638 + + +

      +

      6月29日15点,系统开放结果查询。

      +

      第一志愿录取,一直担心的调剂没有发生。

      +

      7月10日,收到教委短信,系统查询录取通知书。

      +

      + +20220710085749 + + +

      +

      + +20220710084342 + + +

      +

      历时1个多月的幼升小总算落地了。

      +]]>
      +
      +
      +
      diff --git "a/tags/\345\271\274\345\215\207\345\260\217/page/1/index.html" "b/tags/\345\271\274\345\215\207\345\260\217/page/1/index.html" new file mode 100644 index 000000000..9031460a9 --- /dev/null +++ "b/tags/\345\271\274\345\215\207\345\260\217/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E5%B9%BC%E5%8D%87%E5%B0%8F/ + \ No newline at end of file diff --git "a/tags/\346\224\266\345\275\225/index.html" "b/tags/\346\224\266\345\275\225/index.html" new file mode 100644 index 000000000..f3de3b480 --- /dev/null +++ "b/tags/\346\224\266\345\275\225/index.html" @@ -0,0 +1,7 @@ +收录 | 流动 +

      整理下博客的一些调整

      新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。 +更新主题版本,展示文章tag标签 通过对比主题作者的网站,发现使用的不是最新代码。 +...

      2022-05-13 · 1 min · 331 words · Liudon
      \ No newline at end of file diff --git "a/tags/\346\224\266\345\275\225/index.xml" "b/tags/\346\224\266\345\275\225/index.xml" new file mode 100644 index 000000000..f46456a8c --- /dev/null +++ "b/tags/\346\224\266\345\275\225/index.xml" @@ -0,0 +1,48 @@ + + + + 收录 on 流动 + https://liudon.com/tags/%E6%94%B6%E5%BD%95/ + Recent content in 收录 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 13 May 2022 18:20:52 +0800 + + + 整理下博客的一些调整 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + Fri, 13 May 2022 18:20:52 +0800 + https://liudon.com/posts/%E6%95%B4%E7%90%86%E4%B8%8B%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E8%B0%83%E6%95%B4/ + <p>新域名上线一段时间了,通过<code>Google Search Console</code>发现了一些问题,整理下最近进行的一些调整。</p> +<ol> +<li> +<p>更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。</p> + 新域名上线一段时间了,通过Google Search Console发现了一些问题,整理下最近进行的一些调整。

      +
        +
      1. +

        更新主题版本,展示文章tag标签 +通过对比主题作者的网站,发现使用的不是最新代码。

        +

        通过调整Github Actions命令解决:

        +
        - name: Checkout repository
        +    uses: actions/checkout@v2
        +  - name: Checkout submodules
        +    run: git submodule update --init --recursive --remote
        +
      2. +
      3. +

        修正404页面不生效的问题 +主题是自带了404.html文件的,但是部署后没有生成对应文件。

        +

        修改404.html文件内容后解决,具体原因没有深究,感觉是文件内容不是完整html导致。

        +

        可参考文件代码

        +
      4. +
      5. +

        两个域名导致的页面权重问题 +发现有些页面liudon.xyz收录后,liudon.com就不再收录。

        +

        为了规避这种收录问题,将liudon.xyz直接301到了liudon.com上。

        +
      6. +
      +

      目前已调整完毕,观察后续收录情况。

      +]]>
      +
      +
      +
      diff --git "a/tags/\346\224\266\345\275\225/page/1/index.html" "b/tags/\346\224\266\345\275\225/page/1/index.html" new file mode 100644 index 000000000..06b3e040b --- /dev/null +++ "b/tags/\346\224\266\345\275\225/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E6%94%B6%E5%BD%95/ + \ No newline at end of file diff --git "a/tags/\346\227\205\350\241\214/index.html" "b/tags/\346\227\205\350\241\214/index.html" new file mode 100644 index 000000000..5fddedc3f --- /dev/null +++ "b/tags/\346\227\205\350\241\214/index.html" @@ -0,0 +1,9 @@ +旅行 | 流动 +

      一次简短的青岛之行

      刚放暑假的时候,就答应了娃带她去一趟青岛。 +8月份要回老家,所以定在了7月中下旬出发。 +车票/酒店都订好了,结果来了个台风格美。 +出发前一周一直在查天气,就怕去了一直下雨。 +...

      2024-08-31 · 2 min · 998 words · Liudon
      \ No newline at end of file diff --git "a/tags/\346\227\205\350\241\214/index.xml" "b/tags/\346\227\205\350\241\214/index.xml" new file mode 100644 index 000000000..e0b3aa507 --- /dev/null +++ "b/tags/\346\227\205\350\241\214/index.xml" @@ -0,0 +1,123 @@ + + + + 旅行 on 流动 + https://liudon.com/tags/%E6%97%85%E8%A1%8C/ + Recent content in 旅行 on 流动 + Hugo -- 0.134.3 + zh-cn + Sat, 31 Aug 2024 21:24:53 +0800 + + + 一次简短的青岛之行 + https://liudon.com/posts/the-trip-of-qingdao/ + Sat, 31 Aug 2024 21:24:53 +0800 + https://liudon.com/posts/the-trip-of-qingdao/ + <p>刚放暑假的时候,就答应了娃带她去一趟青岛。</p> +<p>8月份要回老家,所以定在了7月中下旬出发。</p> +<p>车票/酒店都订好了,结果来了个台风格美。</p> +<p>出发前一周一直在查天气,就怕去了一直下雨。</p> + 刚放暑假的时候,就答应了娃带她去一趟青岛。

      +

      8月份要回老家,所以定在了7月中下旬出发。

      +

      车票/酒店都订好了,结果来了个台风格美。

      +

      出发前一周一直在查天气,就怕去了一直下雨。

      +

      看了台风的预测路径,感觉可能能赶在台风来之前的空档,硬着头皮出发吧。

      +

      7月26日乘坐高铁G203,中午12点左右到达青岛。

      +

      老天很给面子,是个晴天,还有点晒。

      +

      + +出行计划 + + +

      +

      本来的计划路线:

      +
      第一天:
      +
      +中午到达青岛 -> 天主教堂 -> 栈桥 -> 八大关 -> 第二海水浴场
      +
      +第二天:
      +
      +海底世界/极地海洋馆 -> 石老人海水浴场 -> 五四广场/奥帆中心夜景
      +
      +第三天:
      +
      +酒店休息返京
      +

      按这个路线,定了两个酒店,一个在栈桥附近,一个在海洋馆附近。

      +

      到了青岛后,先去酒店放行李,然后打车去吃饭。

      +

      青岛的第一顿饭,我们选了吃海鲜,事前查了些攻略,选择了栈桥附近的燕欣饭馆。

      +

      + +燕欣饭馆 + + +

      +

      中午很饿,上来就吃,忘记拍照了,只有吃完后的照片了。

      +

      + +一扫而光 + + +

      +

      油焖大虾相当不错,海肠捞饭非常好吃,韭菜很鲜。

      +

      3个人,一共花了240元,非常推荐的一家店。

      +

      吃完饭,溜达到天主教堂打卡。

      +

      + +天主教堂 + + +

      +

      然后是栈桥,人非常多,中午非常晒。

      +

      + +栈桥 + + +

      +

      于是回酒店稍作休息,决定打车直奔第二海水浴场玩水。

      +

      踩水的感觉太好玩了,娃从一开始的有点害怕,到后面追着水玩。

      +

      + +踩水 + + +

      +

      玩完打车去的美团推荐的双合园,地方很小,需要等位。

      +

      吃下来,感觉不如第一顿好吃,菜品一般,不太推荐。

      +

      吃完已经9点了,错过了夜景时间,直接回酒店休息了。

      +

      第二天起床,发现外面下雨了,风很大,最终没逃过台风的影响。

      +

      昨天路过海洋馆,看了外面排队的人巨多,决定不去室内这种海洋馆了。

      +

      先去了银鱼巷溜达一圈,没啥看的,中午在1907青岛老味道吃的午饭,非常不推荐的一家店。

      +

      吃完饭打车到奥帆中心,想着坐船玩一圈,到了发现因为风大停运了。

      +

      + +五四广场打卡 + + +

      +

      打卡完,直奔第三海水浴场玩水。

      +

      到了发现因为风大不让下水了,只能在沙滩上玩沙子了。

      +

      + +第三海水浴场 + + +

      +

      和娃一起抓了几只小螃蟹,虽然天气不好,娃玩的还是很开心。

      +

      + +赶海 + + +

      +

      实在不想吃海鲜了,晚饭在酒店附近吃了个米村拌饭,发现旁边有家类似北京的老年厨房,非常便宜,菜品也不错,第二天早晨在这里解决了。

      +

      天气不好,晚上在酒店看电视了,点了个麦当劳夜宵,结果娃没吃多少,全我吃了,给我撑的。

      +

      第三天天晴了,但是风还是大。

      +

      决定到第二海水浴场看看运气,到了之后还是不让下水,在沙滩上玩了会沙子。

      +

      时间差不多,回酒店办退房。

      +

      然后步行到火车站,上车回京。

      +

      青岛之行就此结束了,尽管行程很短,天气不太好,但一家人在一起就很开心,唯一的教训就是晚上夜宵不要吃的太多。 😂

      +]]>
      +
      +
      +
      diff --git "a/tags/\346\227\205\350\241\214/page/1/index.html" "b/tags/\346\227\205\350\241\214/page/1/index.html" new file mode 100644 index 000000000..40c5d7e28 --- /dev/null +++ "b/tags/\346\227\205\350\241\214/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E6%97%85%E8%A1%8C/ + \ No newline at end of file diff --git "a/tags/\347\210\254\345\261\261/index.html" "b/tags/\347\210\254\345\261\261/index.html" new file mode 100644 index 000000000..c566476d4 --- /dev/null +++ "b/tags/\347\210\254\345\261\261/index.html" @@ -0,0 +1,12 @@ +爬山 | 流动 +

      中秋爬山

      中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。 +晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。 +...

      2024-09-16 · 2 min · 704 words · Liudon

      二刷百望山

      又是周末,娃约了小伙伴一起爬山。 +百望山,二刷走起。 +约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。 +出门晚了,还打不到车,快10点才到。 +小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。 +...

      2022-04-17 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git "a/tags/\347\210\254\345\261\261/index.xml" "b/tags/\347\210\254\345\261\261/index.xml" new file mode 100644 index 000000000..0cd410ad4 --- /dev/null +++ "b/tags/\347\210\254\345\261\261/index.xml" @@ -0,0 +1,140 @@ + + + + 爬山 on 流动 + https://liudon.com/tags/%E7%88%AC%E5%B1%B1/ + Recent content in 爬山 on 流动 + Hugo -- 0.134.3 + zh-cn + Mon, 16 Sep 2024 23:56:16 +0800 + + + 中秋爬山 + https://liudon.com/posts/mid-autumn-festival-climb/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/posts/mid-autumn-festival-climb/ + <p>中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。</p> +<p>晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。</p> + 中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。

      +

      晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。

      +

      园博园开了灯会,微博上看说是人巨多,还是放弃吧。

      +

      跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。

      +

      第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。

      +

      还好不远,山也不高,我们也不着急,就当遛弯。

      +

      来了好几次了,进园就直奔主题:爬。

      +

      这次我俩先走了一段山路,虽然是台阶,但是确实快。

      +

      昨天玩的太累,我们商量着还是继续走坡道吧。

      +

      花了40分钟左右登顶,最快的一次记录了。

      +

      + +登顶 + + +

      +

      爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。

      +

      + +小憩 + + +

      +

      今天天气一般,能见度不高,远处都是灰蒙蒙的。

      +

      + + + + +

      +

      歇到1点多,我俩开始下山。

      +

      之前在微博看到陈晓卿分享的一家新疆馆子白钻美食,决定晚上带娃去尝尝。

      +

      坐了1个半小时的地铁,到了吕营大街。

      +

      高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。

      +

      我俩傻乎乎的爬楼上来,累个够呛。

      +

      建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。

      +

      4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。

      +

      馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂

      +

      不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。

      +

      + +羊腿抓饭 + + +

      +

      + +羊肉串 + + +

      +

      然后又点了一份过油拌面,面条非常劲道,味道非常棒。

      +

      + +过油拌面 + + +

      +

      喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。

      +

      + +砖茶 + + +

      +

      店不大,但是味道挺好,推荐去试试,就是有点远。

      +

      + +白钻美食 + + +

      +

      吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。

      +

      今天是暴走的一天。

      +

      + +健身记录 + + +

      +]]>
      +
      + + 二刷百望山 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + Sun, 17 Apr 2022 22:57:41 +0800 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + <p>又是周末,娃约了小伙伴一起爬山。</p> +<p>百望山,二刷走起。</p> +<p>约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。</p> +<p>出门晚了,还打不到车,快10点才到。</p> +<p>小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。</p> + 又是周末,娃约了小伙伴一起爬山。

      +

      百望山,二刷走起。

      +

      约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。

      +

      出门晚了,还打不到车,快10点才到。

      +

      小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。

      +

      继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。

      +

      这次来,山上明显绿了,花开的更多了,人比上次少多了。

      +

      下山后,又一起吃了个饭,自助吃的有点撑。

      +

      今天天气真好,视野相当不错,就是太晒了。

      +

      + +俯瞰北京 + + +

      +

      + +不知名的树 + + +

      +

      + +山顶 + + +

      +]]>
      +
      +
      +
      diff --git "a/tags/\347\210\254\345\261\261/page/1/index.html" "b/tags/\347\210\254\345\261\261/page/1/index.html" new file mode 100644 index 000000000..8f31d99b1 --- /dev/null +++ "b/tags/\347\210\254\345\261\261/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E7%88%AC%E5%B1%B1/ + \ No newline at end of file diff --git "a/tags/\347\226\253\346\203\205/index.html" "b/tags/\347\226\253\346\203\205/index.html" new file mode 100644 index 000000000..9f0f23cf9 --- /dev/null +++ "b/tags/\347\226\253\346\203\205/index.html" @@ -0,0 +1,14 @@ +疫情 | 流动 +

      疫情下的生活

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。 +昨天看新闻,基本没有社会面新增了,感觉要解封了。 +没想到今天直接被打脸,封控升级了。 +...

      2022-05-20 · 1 min · 189 words · Liudon

      疫情下的五一假期

      五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。 +当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。 +说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。 +...

      2022-05-05 · 1 min · 431 words · Liudon

      被隔离的一周

      从没有想过疫情会离自己这么近,记录一下。 +周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。 +周二早上全员核酸阴性,继续到公司上班。 +...

      2022-04-01 · 1 min · 328 words · Liudon
      \ No newline at end of file diff --git "a/tags/\347\226\253\346\203\205/index.xml" "b/tags/\347\226\253\346\203\205/index.xml" new file mode 100644 index 000000000..1c210abdb --- /dev/null +++ "b/tags/\347\226\253\346\203\205/index.xml" @@ -0,0 +1,91 @@ + + + + 疫情 on 流动 + https://liudon.com/tags/%E7%96%AB%E6%83%85/ + Recent content in 疫情 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 20 May 2022 19:17:57 +0800 + + + 疫情下的生活 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + Fri, 20 May 2022 19:17:57 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu11460346391088325381.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E7%94%9F%E6%B4%BB/20220520-192500@2x_hu3740727864247060874.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220520-192500@2x.png" width="1442" height="924" alt="20220520-192500@2x" title="" loading="lazy" /> + </picture> + +</p> +<p>不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。</p> +<p>昨天看新闻,基本没有社会面新增了,感觉要解封了。</p> +<p>没想到今天直接被打脸,封控升级了。</p> + + +20220520-192500@2x + + +

      +

      不知不觉,北京这一波疫情已经一个月了,目前还是每天50例左右新增。

      +

      昨天看新闻,基本没有社会面新增了,感觉要解封了。

      +

      没想到今天直接被打脸,封控升级了。

      +

      居家办公已经快两周了,也不知道这种日子还要多久。

      +

      在家使用Mac(13寸 m1)办公,屏幕太小,两周下来感觉眼睛要瞎了。

      +

      媳妇帮我想了个办法,投屏到电视上。

      +

      + +20220520194127 + + +

      +

      居家不出门,实在没动力收拾,现在胡子拉碴,一周可能收拾一次。

      +

      不知道这波还要多久。

      +]]>
      +
      + + 疫情下的五一假期 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + Thu, 05 May 2022 20:22:23 +0800 + https://liudon.com/posts/%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BA%94%E4%B8%80%E5%81%87%E6%9C%9F/ + <p>五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。</p> +<p>当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。</p> +<p>说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。</p> + 五一假期前一周,北京疫情又起,说是已经隐蔽传播一周了。

      +

      当天晚上大家就开始屯货了,晚上看着APP里可买的东西一点点没了。

      +

      说实话,出现几粒确诊没有慌,这么抢购搞的有点心慌了,也下单买了点东西。

      +

      毕竟老话说的好,手中有粮,心里不慌。

      +

      第二天出去做核酸,特意去了趟超市,想着再买点肉,结果超市也是各种采购,东西都没了,空手而归。

      +

      工作上五一前有两个大版本的功能要发布,提前1周需求才提,节前这一周忙的要死,还赶上了疫情,好在赶在发布截止日总算发出去了。

      +

      就五一当天带娃去了家旁边的公园遛了下,这次轮滑滑的不错,一直玩到晚上7点才回家。

      +

      夏天到了,北京的杨絮、柳絮各种满天飞,出门真是受罪,其余时间都在家呆着了,偶尔下楼在小区玩会。

      +

      这个假期基本上每天核酸,感觉以后要常态化核酸了。

      +

      老家在假期里突然也疫情又起,县城整个封控了,不过老爸还是回到家了,过程稍微费了点劲。

      +

      突然发现今年的疫情貌似就没消停过,去年五一假期的时候在老家烧烤,今年还不知道什么时候能回家。

      +

      这疫情什么时候是个头啊……

      +]]>
      +
      + + 被隔离的一周 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + Fri, 01 Apr 2022 01:20:39 +0800 + https://liudon.com/posts/%E8%A2%AB%E9%9A%94%E7%A6%BB%E7%9A%84%E4%B8%80%E5%91%A8/ + <p>从没有想过疫情会离自己这么近,记录一下。</p> +<p>周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。</p> +<p>周二早上全员核酸阴性,继续到公司上班。</p> + 从没有想过疫情会离自己这么近,记录一下。

      +

      周一的时候说是有确诊同学来过公司,下午组织全员核酸,做完核酸立马回家。

      +

      周二早上全员核酸阴性,继续到公司上班。

      +

      晚上8点10分刚和同事上地铁,部门群里有人说我们楼有人确诊,过了10分钟,说是大楼管控了,不让出楼了。

      +

      封在楼里的同学统一做核酸,结果出来后才能回家,好多人在公司过了夜。

      +

      周三到公司集体核酸,做完继续居家办公,今天开始公安局、社区开始联系确认信息,公司要求所有人员居家办公,社区反馈需要居家隔离。

      +

      头几天还说对门上封条了,没成想这次轮到自己了。

      +

      到03/31日,隔离解除,健康宝恢复正常。

      +

      第一次体验到健康宝显示弹窗,然后又显示居家隔离。

      +

      第一次体验到被贴封条上门磁。

      +

      第一次体验到鼻拭子,尤其解封前最后一次核酸,双鼻孔。

      +]]>
      +
      +
      +
      diff --git "a/tags/\347\226\253\346\203\205/page/1/index.html" "b/tags/\347\226\253\346\203\205/page/1/index.html" new file mode 100644 index 000000000..cbd0272f6 --- /dev/null +++ "b/tags/\347\226\253\346\203\205/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E7%96%AB%E6%83%85/ + \ No newline at end of file diff --git "a/tags/\347\241\254\347\233\230/index.html" "b/tags/\347\241\254\347\233\230/index.html" new file mode 100644 index 000000000..85468f671 --- /dev/null +++ "b/tags/\347\241\254\347\233\230/index.html" @@ -0,0 +1,8 @@ +硬盘 | 流动 +

      自己动手,更换thinkpad x1硬盘

      电脑突然没法用了,提示"A disk read error occurred"的错误。 +多次重启也不行,感觉是硬盘挂了。 +机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。 +...

      2022-04-22 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git "a/tags/\347\241\254\347\233\230/index.xml" "b/tags/\347\241\254\347\233\230/index.xml" new file mode 100644 index 000000000..e4462ec81 --- /dev/null +++ "b/tags/\347\241\254\347\233\230/index.xml" @@ -0,0 +1,79 @@ + + + + 硬盘 on 流动 + https://liudon.com/tags/%E7%A1%AC%E7%9B%98/ + Recent content in 硬盘 on 流动 + Hugo -- 0.134.3 + zh-cn + Fri, 22 Apr 2022 08:04:18 +0800 + + + 自己动手,更换thinkpad x1硬盘 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + Fri, 22 Apr 2022 08:04:18 +0800 + https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/ + <p>电脑突然没法用了,提示&quot;A disk read error occurred&quot;的错误。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu17019945019773543030.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E6%9B%B4%E6%8D%A2thinkpad-x1%E7%A1%AC%E7%9B%98/491650584526_.pic_hu8176014428739287306.jpg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="491650584526_.pic.jpg" width="1702" height="1276" alt="491650584526_.pic" title="" loading="lazy" /> + </picture> + +</p> +<p>多次重启也不行,感觉是硬盘挂了。</p> +<p>机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。</p> + 电脑突然没法用了,提示"A disk read error occurred"的错误。

      +

      + +491650584526_.pic + + +

      +

      多次重启也不行,感觉是硬盘挂了。

      +

      机器去年过保了,之前有过在售后维护的经历,费用不低,这次决定自己动手。

      +

      + +WechatIMG64 + + +

      +

      一共就5个螺丝,打开后盖。

      +

      + +621650757850_.pic + + +

      +

      可以看到电池旁边的螺丝,拧下取出电池。

      +

      + +631650757850_.pic + + +

      +

      拧开硬盘上的固定螺丝,取出硬盘。

      +

      + +541650584531_.pic + + +

      +

      印象里硬盘还是那种又大又厚的样子,没想到现在都这么小一块了。

      +

      + +WechatIMG55 + + +

      +

      我在京东买的三星970 EVO Plus这款,顺利换上。

      +

      + +611650757849_.pic + + +

      +

      重装系统,搞定,省了一笔,好开心。

      +]]>
      +
      +
      +
      diff --git "a/tags/\347\241\254\347\233\230/page/1/index.html" "b/tags/\347\241\254\347\233\230/page/1/index.html" new file mode 100644 index 000000000..03694e208 --- /dev/null +++ "b/tags/\347\241\254\347\233\230/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E7%A1%AC%E7%9B%98/ + \ No newline at end of file diff --git "a/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.html" "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.html" new file mode 100644 index 000000000..a8528c208 --- /dev/null +++ "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.html" @@ -0,0 +1,8 @@ +自己动手 | 流动 +

      第一次清理键盘

      19年生日的时候,媳妇送了一款flico的机械键盘。 +这次搬家后,想着年前清理下键盘,实在是太脏了。 +周五下班,带上键盘回家。 +...

      2023-01-16 · 1 min · 141 words · Liudon
      \ No newline at end of file diff --git "a/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.xml" "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.xml" new file mode 100644 index 000000000..e5518818b --- /dev/null +++ "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/index.xml" @@ -0,0 +1,87 @@ + + + + 自己动手 on 流动 + https://liudon.com/tags/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B/ + Recent content in 自己动手 on 流动 + Hugo -- 0.134.3 + zh-cn + Mon, 16 Jan 2023 14:43:38 +0800 + + + 第一次清理键盘 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + Mon, 16 Jan 2023 14:43:38 +0800 + https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/ + <p>19年生日的时候,媳妇送了一款flico的机械键盘。</p> +<p>这次搬家后,想着年前清理下键盘,实在是太脏了。</p> +<p>周五下班,带上键盘回家。</p> +<p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu2604963107084465370.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/jpeg" srcset="https://liudon.com/posts/cleaning-the-keyboard-for-the-first-time/202301161450523_hu9101253989535618664.jpeg 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="202301161450523.jpeg" width="1706" height="1279" alt="" title="" loading="lazy" /> + </picture> + +</p> + 19年生日的时候,媳妇送了一款flico的机械键盘。

      +

      这次搬家后,想着年前清理下键盘,实在是太脏了。

      +

      周五下班,带上键盘回家。

      +

      + + + + +

      +

      键盘全貌,上面好多油。

      +

      + + + + +

      +

      开拆。

      +

      + + + + +

      +

      手还是太慢,上工具吧。

      +

      + + + + +

      +

      全部拆完。

      +

      + + + + +

      +

      内部特写。

      +

      + + + + +

      +

      清理出来的灰屑、头发,这键盘见证了我的发迹线变化

      +

      + + + + +

      +

      终于清理干净了。

      +

      + + + + +

      +

      复原,又可以咔咔写代码了。

      +]]>
      +
      +
      +
      diff --git "a/tags/\350\207\252\345\267\261\345\212\250\346\211\213/page/1/index.html" "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/page/1/index.html" new file mode 100644 index 000000000..b4d801bb4 --- /dev/null +++ "b/tags/\350\207\252\345\267\261\345\212\250\346\211\213/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B/ + \ No newline at end of file diff --git "a/tags/\351\201\233\345\250\203/index.html" "b/tags/\351\201\233\345\250\203/index.html" new file mode 100644 index 000000000..9a5f6d621 --- /dev/null +++ "b/tags/\351\201\233\345\250\203/index.html" @@ -0,0 +1,16 @@ +遛娃 | 流动 +

      中秋爬山

      中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。 +晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。 +...

      2024-09-16 · 2 min · 704 words · Liudon
      向日葵

      向日葵

      奥林匹克公园向日葵之旅

      媳妇有事回老家了,这两天自己带娃。 +小区群里有人说奥林匹克公园的向日葵开了,适合拍照。 +正好周六多云,没有太阳,出门遛娃。 +带上我好久不用的相机,省得发霉了。 +...

      2022-07-21 · 1 min · 179 words · Liudon

      二刷百望山

      又是周末,娃约了小伙伴一起爬山。 +百望山,二刷走起。 +约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。 +出门晚了,还打不到车,快10点才到。 +小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。 +...

      2022-04-17 · 1 min · 209 words · Liudon
      \ No newline at end of file diff --git "a/tags/\351\201\233\345\250\203/index.xml" "b/tags/\351\201\233\345\250\203/index.xml" new file mode 100644 index 000000000..134f6f2c5 --- /dev/null +++ "b/tags/\351\201\233\345\250\203/index.xml" @@ -0,0 +1,184 @@ + + + + 遛娃 on 流动 + https://liudon.com/tags/%E9%81%9B%E5%A8%83/ + Recent content in 遛娃 on 流动 + Hugo -- 0.134.3 + zh-cn + Mon, 16 Sep 2024 23:56:16 +0800 + + + 中秋爬山 + https://liudon.com/posts/mid-autumn-festival-climb/ + Mon, 16 Sep 2024 23:56:16 +0800 + https://liudon.com/posts/mid-autumn-festival-climb/ + <p>中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。</p> +<p>晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。</p> + 中秋第一天,娃约了同学在公园玩,骑车、滑轮滑,晚上娃带我去了她说非常好吃的一家店吃饭。

      +

      晚上回来,想着第二天在家呆一天也是看电视,打算带她爬长城,查了一下,往返的票早已售罄,只好放弃这个方案。

      +

      园博园开了灯会,微博上看说是人巨多,还是放弃吧。

      +

      跟娃商量了一下,最后我们决定还是爬山,再刷一次百望山。

      +

      第二天一早睡到9点,起床洗漱,吃早饭,收拾东西,出门的时候都已经10点半了。

      +

      还好不远,山也不高,我们也不着急,就当遛弯。

      +

      来了好几次了,进园就直奔主题:爬。

      +

      这次我俩先走了一段山路,虽然是台阶,但是确实快。

      +

      昨天玩的太累,我们商量着还是继续走坡道吧。

      +

      花了40分钟左右登顶,最快的一次记录了。

      +

      + +登顶 + + +

      +

      爬到山顶,找了个阴凉,补充能量,娃请我吃雪糕。

      +

      + +小憩 + + +

      +

      今天天气一般,能见度不高,远处都是灰蒙蒙的。

      +

      + + + + +

      +

      歇到1点多,我俩开始下山。

      +

      之前在微博看到陈晓卿分享的一家新疆馆子白钻美食,决定晚上带娃去尝尝。

      +

      坐了1个半小时的地铁,到了吕营大街。

      +

      高德导航说是十里河K2出口出站,这个口确实离的近,但是出站需要爬超级长的楼梯。

      +

      我俩傻乎乎的爬楼上来,累个够呛。

      +

      建议选择K1出站口,电梯出站,上来跟K2出口挨着,没有多远。

      +

      4点多到店,一开始还担心不到饭点有没有饭,进门一看已经有几桌在吃饭的了。

      +

      馆子里吃饭的维族人不少,看来确实是本地特色,吃完饭娃还问我是不是外国人。😂

      +

      不知道份量,先点了羊腿抓饭和羊肉串,娃说好吃,推荐的烤包子已经没有了。

      +

      + +羊腿抓饭 + + +

      +

      + +羊肉串 + + +

      +

      然后又点了一份过油拌面,面条非常劲道,味道非常棒。

      +

      + +过油拌面 + + +

      +

      喝了据说是本地的茶,查了一下,说是叫“砖茶”,免费的。

      +

      + +砖茶 + + +

      +

      店不大,但是味道挺好,推荐去试试,就是有点远。

      +

      + +白钻美食 + + +

      +

      吃完地铁回家,等公交的时候错过了一趟车,还多等了半个小时。

      +

      今天是暴走的一天。

      +

      + +健身记录 + + +

      +]]>
      +
      + + 奥林匹克公园向日葵之旅 + https://liudon.com/posts/olympic-park-sunflower-tour/ + Thu, 21 Jul 2022 20:40:20 +0800 + https://liudon.com/posts/olympic-park-sunflower-tour/ + <p>媳妇有事回老家了,这两天自己带娃。</p> +<p>小区群里有人说奥林匹克公园的向日葵开了,适合拍照。</p> +<p>正好周六多云,没有太阳,出门遛娃。</p> +<p>带上我好久不用的相机,省得发霉了。</p> + 媳妇有事回老家了,这两天自己带娃。

      +

      小区群里有人说奥林匹克公园的向日葵开了,适合拍照。

      +

      正好周六多云,没有太阳,出门遛娃。

      +

      带上我好久不用的相机,省得发霉了。

      +

      以往都是去的南园,第一次来北园。

      +

      西门进入,沿着路往右走,一会就到。

      +

      人很多,估计大家都因为之前疫情在家憋疯了。

      +

      到了没多久,太阳又出来了,超级晒。

      +

      提醒一下,看见向日葵园后,继续往前走,前面有观景台,更适合拍照。

      +

      + +向日葵 + + + + + +向日葵 + + + + + +荷花 + + + + + +荷叶上的蜻蜓 + + +

      +]]>
      +
      + + 二刷百望山 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + Sun, 17 Apr 2022 22:57:41 +0800 + https://liudon.com/posts/%E4%BA%8C%E5%88%B7%E7%99%BE%E6%9C%9B%E5%B1%B1/ + <p>又是周末,娃约了小伙伴一起爬山。</p> +<p>百望山,二刷走起。</p> +<p>约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。</p> +<p>出门晚了,还打不到车,快10点才到。</p> +<p>小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。</p> + 又是周末,娃约了小伙伴一起爬山。

      +

      百望山,二刷走起。

      +

      约好了9点半门口见面,早上睁眼8点了,赶紧起床洗漱吃饭。

      +

      出门晚了,还打不到车,快10点才到。

      +

      小伙伴和人爸爸已经先爬上去了,带着娃一路小跑,20分钟从大门爬到山上。

      +

      继续赶路爬楼梯,总算登上了观景台,俩小朋友终于见面了,一人奖励一个冰激凌。

      +

      这次来,山上明显绿了,花开的更多了,人比上次少多了。

      +

      下山后,又一起吃了个饭,自助吃的有点撑。

      +

      今天天气真好,视野相当不错,就是太晒了。

      +

      + +俯瞰北京 + + +

      +

      + +不知名的树 + + +

      +

      + +山顶 + + +

      +]]>
      +
      +
      +
      diff --git "a/tags/\351\201\233\345\250\203/page/1/index.html" "b/tags/\351\201\233\345\250\203/page/1/index.html" new file mode 100644 index 000000000..3bf95d5f6 --- /dev/null +++ "b/tags/\351\201\233\345\250\203/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E9%81%9B%E5%A8%83/ + \ No newline at end of file diff --git "a/tags/\351\235\236\344\272\254\347\261\215/index.html" "b/tags/\351\235\236\344\272\254\347\261\215/index.html" new file mode 100644 index 000000000..3ff1cd9d6 --- /dev/null +++ "b/tags/\351\235\236\344\272\254\347\261\215/index.html" @@ -0,0 +1,7 @@ +非京籍 | 流动 +

      记录2022年海淀幼升小

      18年的热点新闻,纳税千万孩子无法在北京上学。 +一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。 +...

      2022-05-25 · 2 min · 715 words · Liudon
      \ No newline at end of file diff --git "a/tags/\351\235\236\344\272\254\347\261\215/index.xml" "b/tags/\351\235\236\344\272\254\347\261\215/index.xml" new file mode 100644 index 000000000..72607ebf6 --- /dev/null +++ "b/tags/\351\235\236\344\272\254\347\261\215/index.xml" @@ -0,0 +1,129 @@ + + + + 非京籍 on 流动 + https://liudon.com/tags/%E9%9D%9E%E4%BA%AC%E7%B1%8D/ + Recent content in 非京籍 on 流动 + Hugo -- 0.134.3 + zh-cn + Wed, 25 May 2022 20:10:01 +0800 + + + 记录2022年海淀幼升小 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + Wed, 25 May 2022 20:10:01 +0800 + https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/ + <p> + +<picture><source type="image/webp" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu3469823060694782874.webp 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><source type="image/png" srcset="https://liudon.com/posts/%E8%AE%B0%E5%BD%952022%E5%B9%B4%E6%B5%B7%E6%B7%80%E5%B9%BC%E5%8D%87%E5%B0%8F/20220525202612_hu18419126202489954049.png 1080w" sizes="(min-width: 768px) 1080px, 100vw" /><img src="20220525202612.png" width="1920" height="2243" alt="20220525202612" title="" loading="lazy" /> + </picture> + +</p> +<blockquote> +<p>18年的热点新闻,纳税千万孩子无法在北京上学。</p> +</blockquote> +<p>一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。</p> + + +20220525202612 + + +

      +
      +

      18年的热点新闻,纳税千万孩子无法在北京上学。

      +
      +

      一直听说外地人在北京上学难,今年娃到了上小学的年纪,也算真实的体验了一把上学的难。

      +

      提前在网上搜了一番资料,都是一些机构整理的信息。

      +

      没有找到具体分享的记录,自己整理了下,希望能帮助到其他人。

      +

      1. 信息采集

      +

      5月5日,采集系统开放。

      +

      当天下午录入相关信息,提交网上审核。

      +

      2. 网上审核

      +

      信息提交后,就开始了漫长的审核时间。

      +
      5月06日 户口信息审核通过
      +5月14日 居住证审核通过
      +5月16日 居住证明审核通过
      +5月19日 工作证明审核通过
      +

      + +20220527202411 + + +

      +

      3. 线下审核

      +

      网上审核通过后,打印入学申请表,预约线下审核时间。

      +

      + +20220527202458 + + +

      +

      这里还有一个插曲,本来以为线下审核是在社区居委会。

      +

      周六的时候,在小学入学群里聊天,有人说是在学区审核。

      +

      后来交流一番后,发现是自己理解错了。

      +

      这里是要先去社区审核盖章,然后再到学区交资料走线下审核。

      +
      5月22日 社区居委会审核盖章
      +5月25日 到街道递交审核材料
      +

      + +街道审核 + + +

      +
        +
      • 如果你是外地集体户口,需要准备集体户口首页(复印件需要加盖公章)。
      • +
      • 工作证明还需要提供满足时间要求的社保缴费记录。
      • +
      +

      4. 审核通过

      +

      5月27日,审核通过后,打印信息采集表。

      +

      + +20220527202545 + + +

      +

      5. 学校登记

      +

      6月1日对口学校发布入学登记通知书。

      +

      按通知书准备资料,到登记时间去学校交资料。

      +

      今年遇到疫情,改为线上邮件发送资料登记了。

      +

      6. 填报志愿

      +

      6月23日,海淀教育发文1911后填报志愿通知

      +

      + +20220629215840 + + +

      +

      第一志愿锁定,其他志愿自己选择填报。

      +

      6月25日锁定,不允许再修改。

      +
      +

      网上消息,第一志愿锁定,说明有1911后名额,有机会选中。

      +

      租房的不需要填报志愿,等待派位。

      +
      +

      7. 查看结果

      +

      + +20220629220638 + + +

      +

      6月29日15点,系统开放结果查询。

      +

      第一志愿录取,一直担心的调剂没有发生。

      +

      7月10日,收到教委短信,系统查询录取通知书。

      +

      + +20220710085749 + + +

      +

      + +20220710084342 + + +

      +

      历时1个多月的幼升小总算落地了。

      +]]>
      +
      +
      +
      diff --git "a/tags/\351\235\236\344\272\254\347\261\215/page/1/index.html" "b/tags/\351\235\236\344\272\254\347\261\215/page/1/index.html" new file mode 100644 index 000000000..88ad82593 --- /dev/null +++ "b/tags/\351\235\236\344\272\254\347\261\215/page/1/index.html" @@ -0,0 +1,2 @@ +https://liudon.com/tags/%E9%9D%9E%E4%BA%AC%E7%B1%8D/ + \ No newline at end of file