<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Joeson Huang&apos;s Tech Space</title>
    <description>Working on Go and React mostly. Into all kinds of devop and infra stuff as well!
</description>
    <link>https://joeosnw.github.io/</link>
    <atom:link href="https://joeosnw.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 28 May 2025 23:52:58 +0000</pubDate>
    <lastBuildDate>Wed, 28 May 2025 23:52:58 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Cool Golang slog.Logger tricks</title>
        <description>&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;For years in Go, I’ve used many different logging libraries, from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logrus&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zap&lt;/code&gt;.
After the release of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slog&lt;/code&gt; into Go standard library, I immediately embraced it.
It’s clever design made it so versatile and easy to use, and has already accumulated an enormous amount of libraries in the ecosystem.&lt;/p&gt;

&lt;h2 id=&quot;trick-1&quot;&gt;Trick 1&lt;/h2&gt;

&lt;p&gt;Try have a dedicate package in your project for logging attributes.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;you have broad view of what attributes are available, especially when you are setting up logging indexes and queries in your dashboards.&lt;/li&gt;
  &lt;li&gt;it enforces people using a consistent way of naming logging attributes, especially in large teams, you won’t end up with  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userID&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_id&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserID&lt;/code&gt; in your logs at the same time.&lt;/li&gt;
  &lt;li&gt;it provides a easy gateway of implementing something even greater, which I’ll show you in trick number 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;e.g,&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;trick-2&quot;&gt;Trick 2&lt;/h2&gt;

&lt;p&gt;Combining logging attributes and error logs&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ErrorWithAttrs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;   &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorWithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorWithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorWithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AttrsFromError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;any&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attr&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way, you will be able to do something like this:&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SearchDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userID&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user %v not found in database due to: %w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SearchDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;myLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;failed to get user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This  will produce a log entry like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;level&quot;:&quot;error&quot;,&quot;msg&quot;:&quot;failed to get user&quot;,&quot;user_id&quot;:123,&quot;error&quot;:&quot;user user_id=123 not found in database due to: &amp;lt;original error&amp;gt;&quot;}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You get the benefits of:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a. able to get all related details in error message&lt;/li&gt;
  &lt;li&gt;b. has a separate attribute field where you can index and query on in dashboards like kibana.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;trick-3&quot;&gt;Trick 3&lt;/h1&gt;

&lt;p&gt;A less easy way of using loggers all over the places. Sometimes it’s just pain to pass loggers around, especially when you want to log as much as possible.&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;context&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;log/slog&quot;&lt;/span&gt;
	&lt;span class=&quot;s&quot;&gt;&quot;sync&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pendingLogs&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pendingLogsMu&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mutex&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Provide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pendingLogsMu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pendingLogs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pendingLogs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pendingLogsMu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;instrument&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;once&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;sync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Once&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;once&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Do&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithGroup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;pendingLogsMu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Lock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;pendingLogs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pendingLogs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;pendingLogsMu&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WithGroup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;globalHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithGroup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholderHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with this, you can create a logger like this:&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-special-library&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithAttrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DoSomething&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;doing something&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and just provide a handler at the entry point of your application once and for all:&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;logging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Provide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewJSONHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 28 May 2025 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2025/05/28/cool-go-slog-tricks.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2025/05/28/cool-go-slog-tricks.html</guid>
        
        
      </item>
    
      <item>
        <title>Shapez 2 reusable blocks, all 100% efficiency</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This assumes you upgrades are at the same level. (cutting and stackgins at 1/4 belt speed)&lt;/p&gt;

&lt;/blockquote&gt;
&lt;style&gt;
  pre {
    white-space: pre-wrap;       /* Since CSS 2.1 */
    white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
    white-space: -pre-wrap;      /* Opera 4-6 */
    white-space: -o-pre-wrap;    /* Opera 7 */
    word-wrap: break-word;       /* Internet Explorer 5.5+ */
  }
&lt;/style&gt;

&lt;p&gt;&lt;img src=&quot;/images/shapez2/Rotator.webp&quot; type=&quot;image/webp&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;90 degree rotator, buttons for toggle clockwise/counter-clockwise, and output to straight or side&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Show Code&lt;/summary&gt;

  &lt;pre&gt;
  SHAPEZ2-1-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$
  &lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;&lt;img srcset=&quot;/images/shapez2/Painter.webp&quot; type=&quot;image/webp&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Paint shapes, button for toggle output to straight or side&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Show Code&lt;/summary&gt;

   &lt;pre&gt;
  SHAPEZ2-1-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$
  &lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;&lt;img srcset=&quot;/images/shapez2/Splitter.webp&quot; type=&quot;image/webp&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Cutter/Splitter, emit both halves, buttons for toggle halves to go for each side&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Show Code&lt;/summary&gt;

   &lt;pre&gt;
  SHAPEZ2-1-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$
  &lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;&lt;img srcset=&quot;/images/shapez2/Stacker.webp&quot; type=&quot;image/webp&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Stack two shapes, button for toggle which input goes on top (default straight in bottom)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Show Code&lt;/summary&gt;

   &lt;pre&gt;
  SHAPEZ2-1-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$
  &lt;/pre&gt;
&lt;/details&gt;

&lt;p&gt;&lt;img srcset=&quot;/images/shapez2/Cornerer.webp&quot; type=&quot;image/webp&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Split shapes and stacked together, get output of NE and SW quaters.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;details&gt;
  &lt;summary&gt;Show Code&lt;/summary&gt;

   &lt;pre&gt;
  SHAPEZ2-1-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$
   &lt;/pre&gt;
&lt;/details&gt;
</description>
        <pubDate>Sun, 17 Nov 2024 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2024/11/17/shapez2-reusable-blocks.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2024/11/17/shapez2-reusable-blocks.html</guid>
        
        
      </item>
    
      <item>
        <title>(草稿WIP) 从头打造全屋智能家庭 (持续更新), Part II - 硬件选择, 实操以及注意事项</title>
        <description>&lt;h1 id=&quot;硬件选择&quot;&gt;硬件选择&lt;/h1&gt;

&lt;blockquote&gt;
  &lt;p&gt;具体的智能硬件方面目前是 Aqara (绝大部分) 和 Tuya, 可以完美的通过 Zigbee 配对, 离线控制. 而且 Zigbee2MQTT 里面可以直接 OTA 升级硬件官方固件. 下面介绍的都是一些周边的硬件.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;uunifi-dream-machine-pro-special-edition-udm-pro-se&quot;&gt;Uunifi Dream Machine PRO Special Edition, UDM-PRO-SE&lt;/h4&gt;
&lt;p&gt;核心网络设备, 因为有各个房间ap面板(目前还没安装), 和自己托管的监控方案的需求. 直接一步到位购入了 UDM-PRO-SE, 其中 SE 是升级版, 多了POE和POE+供电, 正好适用于摄像头和AP. 不用再买一台POE的交换机(如果设备不多的话)&lt;/p&gt;

&lt;p&gt;Unifi 的产品最方便的是可以无缝接入 Home Assistant, 包括客户端信息 (是否联网, 所在AP). Unifi Protect 也可以无缝接入, 不仅仅是有视频源, 还有摄像头自带的动作侦测, 开启红外等功能均已接入&lt;/p&gt;

&lt;p&gt;入坑指南: https://www.chiphell.com/thread-2421885-1-1.html&lt;/p&gt;

&lt;h4 id=&quot;sonoff-zbdongle-plus&quot;&gt;SONOFF ZBDongle-Plus&lt;/h4&gt;
&lt;p&gt;Zigbee天线, 大概100左右. 支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee 3.0&lt;/code&gt; 协议. 即插即用, 固件更新也很方便, 可以直接使用 PC/树莓派 等设备直接通过 usb 更新. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee2MQTT&lt;/code&gt; 完美支持.&lt;/p&gt;

&lt;h4 id=&quot;n5105-nuc-11-pc&quot;&gt;N5105 NUC 11 PC&lt;/h4&gt;
&lt;p&gt;此 NUC 只花费了1000元, 有4核8G, 用来运行 Home Assistant 再合适不过 (因为没预算了😂). Zigbee Dongle 便插在此机器上&lt;/p&gt;

&lt;h4 id=&quot;智能硬件&quot;&gt;智能硬件&lt;/h4&gt;
&lt;blockquote&gt;
  &lt;p&gt;目前已购买使用的有如下这些, 已经全部通过 Zigbee 接入(没有使用 Aqara 或米家 app)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Aqara系的&lt;/li&gt;
  &lt;li&gt;智能电动窗帘电机zigbee版&lt;/li&gt;
  &lt;li&gt;门窗传感器 E1&lt;/li&gt;
  &lt;li&gt;水浸传感器 E1&lt;/li&gt;
  &lt;li&gt;温湿度传感器&lt;/li&gt;
  &lt;li&gt;烟雾报警器&lt;/li&gt;
  &lt;li&gt;智能天然气报警器&lt;/li&gt;
  &lt;li&gt;智能无线墙壁开关 D1&lt;/li&gt;
  &lt;li&gt;墙壁开关D1版-零火三键&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;实操&quot;&gt;实操&lt;/h1&gt;

&lt;h3 id=&quot;安装-home-assistant&quot;&gt;安装 Home Assistant&lt;/h3&gt;
&lt;blockquote&gt;
  &lt;p&gt;https://www.home-assistant.io/installation/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果是使用 RaspberryPi, 可以直接使用官方 image 直接烧录.&lt;/p&gt;

&lt;p&gt;我由于还要在机器上单独装 Prometheus, Grafana 等服务, 所以是在 Debian 的基础上安装的. 如果你也是在现有系统上装 Home Assistant 的话, 强烈建议使用 Supervised 的方式安装, 这样后续安装 Addon 的时候可以网页 GUI 一键安装. 步骤大致如下:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#1 如果没有安装 Docker 的话&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ca-certificates curl gnupg lsb-release &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /etc/apt/keyrings
curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/debian/gpg | &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gpg &lt;span class=&quot;nt&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;deb [arch=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;--print-architecture&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;lsb_release &lt;span class=&quot;nt&quot;&gt;-cs&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; stable&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-compose-plugin &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#2 安装 Home Assistant os-agent&lt;/span&gt;
wget https://github.com/home-assistant/os-agent/releases/download/1.2.2/os-agent_1.2.2_linux_x86_64.deb
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; os-agent_1.2.2_linux_x86_64.deb
gdbus introspect &lt;span class=&quot;nt&quot;&gt;--system&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dest&lt;/span&gt; io.hass.os &lt;span class=&quot;nt&quot;&gt;--object-path&lt;/span&gt; /io/hass/os &lt;span class=&quot;c&quot;&gt;#确认安装成功&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;#3 安装 Home Assistant Supervised&lt;/span&gt;
apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jq wget curl udisks2 libglib2.0-bin network-manager dbus &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
wget https://github.com/home-assistant/supervised-installer/releases/latest/download/homeassistant-supervised.deb
dpkg &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; homeassistant-supervised.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;具体还可以参考 &lt;a href=&quot;https://github.com/home-assistant/supervised-installer&quot;&gt;这里&lt;/a&gt; &lt;a href=&quot;https://www.home-assistant.io/installation/linux#install-home-assistant-supervised&quot;&gt;这里&lt;/a&gt; 还有 &lt;a href=&quot;https://github.com/home-assistant/os-agent/tree/main#using-home-assistant-supervised-on-debian=&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;小小的电路改造&quot;&gt;小小的电路改造&lt;/h3&gt;

&lt;p&gt;由于现在的房子都是精装修, 然后又懒, 又不想多花钱, 就没重新装修了.&lt;/p&gt;

&lt;p&gt;那么带来一个问题, 开关盒(暗盒)里面都是没有零线的, 而我又想要使用 &lt;em&gt;零火&lt;/em&gt; 版智能开关. 于是乎把开关盒里面一根控制氛围灯的线给征用了, 把氛围灯给淘汰掉了.&lt;/p&gt;

&lt;p&gt;原本的走线如下:
 &lt;img src=&quot;/images/img-build-smart-home-from-scratch-part-2-4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其中, 红色为火线(L), 蓝线为零线(N), 黑色为控制线. 棕色为原本的开关, 紫色为 &lt;em&gt;零火&lt;/em&gt; 版智能开关.&lt;/p&gt;

&lt;p&gt;可以看见, 开关盒里面只有火线进来, 和灯的控制线出去, 是没有零线的. 但每个灯泡上是肯定有一根零线的. 于是如下图, 我们可以这样改造:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/img-build-smart-home-from-scratch-part-2-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个就是把其中一个灯泡的零线给借用过来, 把原本就存在的控制线给短接. 这样实质上这根控制线就是一根零线了.&lt;/p&gt;

&lt;p&gt;下一期我会把安装另一个开关盒的视频尝试给录制剪辑后放上来供大家参考.&lt;/p&gt;

&lt;h1 id=&quot;注意事项&quot;&gt;注意事项&lt;/h1&gt;

&lt;h3 id=&quot;如果还在装修或者还没有装修-一定要在开关盒里加零线&quot;&gt;如果还在装修或者还没有装修, &lt;strong&gt;一定要在开关盒里加零线&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;如果开关盒里没有零线的话, 就只能用单火的智能开关 (原理图如下). 单火的智能开关在物理上是一直有电 (没有零线, 可以理解为DC里面的地线/0V Ref), 一般是一个可调电阻来通断. 所以终端 — 灯泡会在开关断开后也会微弱的闪 (取决于灯泡和开关质量).
而零火的开关是真正的通断, 控制电路是通过继电器来进行物理的通断.
&lt;img src=&quot;/images/img-build-smart-home-from-scratch-part-2-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;双-16a-插座-或者-16a--10a&quot;&gt;双 16A 插座 (或者 16A + 10A)&lt;/h3&gt;
&lt;p&gt;因为智能窗帘的电机一般是插空调的插座上, 但是一般只留了一个 16A 三孔插座, 窗帘电机(两台电机可以串联)也需要一个三孔插座. 所以我自己买了一个双三孔(兼容16A和10A)的86面板替换.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/img-build-smart-home-from-scratch-part-2-2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;升级-sonoff-zbdongle&quot;&gt;升级 SONOFF ZBDongle&lt;/h3&gt;

&lt;p&gt;部分智能硬件产品对 Zigbee Dongle 插件版本有要求, 需要升级, 如下是我用到的方法&lt;/p&gt;

&lt;p&gt;视频: https://www.youtube.com/watch?v=iCE5Z43EKpk&lt;/p&gt;

&lt;p&gt;固件: https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator/Z-Stack_3.x.0/bin  一定要下载 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CC1352P2_CC2652P_launchpad_*.zip&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;脚本: https://github.com/JelmerT/cc2538-bsl 如果脚本没跑成功, 多半是依赖没装, &lt;strong&gt;记得要把 Zigbee2MQTT Addon 停用&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;文本教程: https://sonoff.tech/wp-content/uploads/2022/01/SONOFF-Zigbee-3.0-USB-dongle-plus-firmware-flashing-.pdf&lt;/p&gt;

&lt;h3 id=&quot;添加-homekit-的智能设备到-home-assistant-中&quot;&gt;添加 HomeKit 的智能设备到 Home Assistant 中&lt;/h3&gt;
&lt;p&gt;需要一个已存在的 HomeKit 家庭 (通过 iPad 或者 Homepod). 通过手机扫码将设备加入到 HomeKit 中后, 然后去 Home App 中将此设备移除. 此时该设备会处于待发现状态, Home Assistant 便可将其 Adopt.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(未完待续…)&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 17 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2022/07/17/build-smart-home-from-scratch-part-2.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2022/07/17/build-smart-home-from-scratch-part-2.html</guid>
        
        
      </item>
    
      <item>
        <title>从头打造全屋智能家庭 (持续更新), Part I - 网关平台/协议及软件的选择</title>
        <description>&lt;p&gt;先上个全家福
&lt;img src=&quot;/images/img-build-smart-home-from-scratch-part-1-1.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;序&quot;&gt;序&lt;/h2&gt;

&lt;p&gt;在此之前, 我已经在租来的房间中尝试了多种智能家居以及网关系统. 此次借搬入新买的房子的之机, 我将从头打造一个全屋智能的智慧家庭. 然后也记录下来, 与君共享. 一个是分享当中一些误区, 坑点. 二是以后改进时有据可循.&lt;/p&gt;

&lt;h2 id=&quot;内容规划&quot;&gt;内容规划&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;网关平台/协议选择&lt;/li&gt;
  &lt;li&gt;软件选择&lt;/li&gt;
  &lt;li&gt;硬件选择&lt;/li&gt;
  &lt;li&gt;实操&lt;/li&gt;
  &lt;li&gt;电路改造?&lt;/li&gt;
  &lt;li&gt;待续&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;网关平台协议选择&quot;&gt;网关平台/协议选择&lt;/h2&gt;

&lt;h1 id=&quot;平台&quot;&gt;平台&lt;/h1&gt;

&lt;p&gt;目前市面上每个厂商都有自己的智能家居平台, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;米家&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;涂鸦&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;博联&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Google Home&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alexa&lt;/code&gt; 等一众平台厂商可供选择.&lt;/p&gt;

&lt;h1 id=&quot;协议&quot;&gt;协议&lt;/h1&gt;

&lt;p&gt;通讯协议分别又有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Wif&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;蓝牙&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee,&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z-wave&lt;/code&gt; 等各类协议. 其中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Z-wave&lt;/code&gt; 因为使用的 &lt;em&gt;865-926 MHz&lt;/em&gt; 波段在国内不属于公共领域, 使用起来是违法的, 所以我们不能考虑. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Wifi&lt;/code&gt; 和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;蓝牙&lt;/code&gt;的大部分设备都不支持完全离线运行, 需要定时和云端的服务器通信来刷新令牌. 当断开网络过久, 令牌过期时便不受控制.&lt;/p&gt;

&lt;p&gt;所以, 我选择了使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 的智能设备. 一是因为支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt;的设备市面比较多, 我买过的有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Aqara (绿米)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;涂鸦系&lt;/code&gt;两大厂牌的设备均是支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议的. 二是使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议的设备不需要使用厂家的任何网关, App, 云服务. 只需本地一个支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议的任意网关设备即可. 而且 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议是可以 &lt;em&gt;Mesh&lt;/em&gt; 的, 任意接通有线电源的设备均可作为一个 &lt;strong&gt;路由节点&lt;/strong&gt;(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议中的 &lt;strong&gt;信号中继节点&lt;/strong&gt; ).&lt;/p&gt;

&lt;p&gt;唯一有可能有影响的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 使用的是 &lt;em&gt;2.4 GHz&lt;/em&gt; 波段, 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2.4G Wifi&lt;/code&gt; 有冲突的可能. 但是根据我的使用经验, 影响可以说是有限的. 除非你有很多的 AP 重叠在一块.&lt;/p&gt;

&lt;h2 id=&quot;软件选择&quot;&gt;软件选择&lt;/h2&gt;

&lt;h1 id=&quot;前端&quot;&gt;前端&lt;/h1&gt;

&lt;p&gt;所谓前端, 便是与用户打交道的这一块. 由于我是重度苹果用户, 自然而然的便选择了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt; 作为交互界面. 可以通过 &lt;em&gt;HomePod&lt;/em&gt;  和 &lt;em&gt;Apple TV&lt;/em&gt; 来进行语音交互. 而且手机中的 &lt;em&gt;Home App&lt;/em&gt; 天然的支持在外网访问 (不在家中时), 安全也是相对的有保障.&lt;/p&gt;

&lt;p&gt;当然了, 下面会提及到使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 作为控制核心, 其本身本就可以支持不同的控制前端, 小度, 小爱等主流厂商也都是可以无缝集成.&lt;/p&gt;

&lt;h1 id=&quot;后端&quot;&gt;后端&lt;/h1&gt;

&lt;p&gt;虽然可以直接 All In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt;, 但是使用体验下来其自动化方面, 还有支持的设备方面可以说是比较弱的. 所以很早我便开始了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 的尝试, 中间只是作为第三方硬件通过 &lt;em&gt;HomeKit Accessory Protocol&lt;/em&gt; (&lt;em&gt;HAP&lt;/em&gt;)  接入到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt; 生态. 然后在一年的使用当中发现, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt; 对于自动化的支持并不是很全面和易用 (例如夜晚分时段调整空调温度, 在 iOS 15之前只能通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home+&lt;/code&gt; 这个第三方 App 来编写).&lt;/p&gt;

&lt;p&gt;虽然说后端选择了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt;, 但是由于其强大的扩展性, 我们相当于只是选择了一个智能家居的操作系统. 还有很多 &lt;em&gt;软件&lt;/em&gt;(Addons &amp;amp; Integrations) 需要我们选择.&lt;/p&gt;

&lt;p&gt;我接下来会介绍几个非常核心的必备 &lt;em&gt;软件&lt;/em&gt;(&lt;strong&gt;Addons&lt;/strong&gt; &amp;amp; &lt;strong&gt;Integrations&lt;/strong&gt;).&lt;/p&gt;

&lt;h4 id=&quot;zigbee2mqtt-addon-和-mosquitto-addon--integration&quot;&gt;Zigbee2Mqtt (Addon) 和 mosquitto (Addon &amp;amp; Integration)&lt;/h4&gt;

&lt;p&gt;在上面说到, 我们选择了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 作为通讯协议. 但要将其接入到智能家居系统中, 我们还需要一些额外的操作. 其中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mosquitto&lt;/code&gt; 是一个 &lt;em&gt;MQTT Broker&lt;/em&gt;,  消息队列. 我们需要同时装 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mosquitto&lt;/code&gt;的 &lt;strong&gt;Addon&lt;/strong&gt; 和 &lt;strong&gt;Integration&lt;/strong&gt;. &lt;strong&gt;Addon&lt;/strong&gt; 是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mosquitto&lt;/code&gt; 本身. &lt;strong&gt;Integration&lt;/strong&gt; 是连接 &lt;em&gt;MQTT&lt;/em&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt;的桥梁, 将设备注册消息转化为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 的 &lt;strong&gt;Entity&lt;/strong&gt;, 及相关的设备读写操作.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee2Mqtt&lt;/code&gt; 则是将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 协议的设备通信同步到 &lt;em&gt;MQTT&lt;/em&gt;(接下来硬件篇也会提及), 这样完成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zigbee&lt;/code&gt; 设备到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 的闭环.&lt;/p&gt;

&lt;h4 id=&quot;node-red-addon&quot;&gt;Node-RED (Addon)&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 虽然支持自动化脚本编写, 但是由于其 yaml 的组织方式, 编辑起来并不是特别的好理解, 调试.  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Node-RED&lt;/code&gt; 是一个图形化节点编程工具. 而这个 &lt;strong&gt;Addon&lt;/strong&gt; 可以让你通过图形化节点编程的方式来组织你的自动化流程, 而且可以方便的调试, debug.&lt;/p&gt;

&lt;h4 id=&quot;homekit-integration&quot;&gt;HomeKit (Integration)&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt; &lt;strong&gt;Integration&lt;/strong&gt; 是打通 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomeKit&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Home Assistant&lt;/code&gt; 的桥梁.&lt;/p&gt;

&lt;h4 id=&quot;apple-tv-integration&quot;&gt;Apple TV (Integration)&lt;/h4&gt;

&lt;p&gt;虽然这个 &lt;strong&gt;Integration&lt;/strong&gt; 叫做 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Apple TV&lt;/code&gt;, 但是其也可以整合 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomePod&lt;/code&gt;. 这个 &lt;strong&gt;Integration&lt;/strong&gt; 可以让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Apple TV&lt;/code&gt; 或 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HomePod&lt;/code&gt; 作为一个媒体播放器, 可以将监控视频通过 &lt;em&gt;Airplay&lt;/em&gt; 协议投屏, 也可以使用 &lt;em&gt;TTS&lt;/em&gt; 实时生成语音投放.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(未完待续…)&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 04 Jul 2022 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2022/07/04/build-smart-home-from-scratch-part-1.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2022/07/04/build-smart-home-from-scratch-part-1.html</guid>
        
        
      </item>
    
      <item>
        <title>Go Pitfalls and Patterns</title>
        <description>&lt;h2 id=&quot;1-如何获取interface的reflecttype&quot;&gt;1. 如何获取interface的reflect.Type&lt;/h2&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duck&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Quack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflectTypeOfDuck&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Duck&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IsDuck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Implements&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reflectTypeOfDuck&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-不要用mapstringinterface来unmarshal&quot;&gt;2. 不要用map[string]interface{}来Unmarshal&lt;/h2&gt;
&lt;p&gt;避免下面的用法&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 这里转换失败会panic, 除非 message, ok := m[&quot;message&quot;].(string)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;尝试使用下面的用法&lt;/p&gt;
&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;message&quot;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}{}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-defer时机&quot;&gt;3. defer时机&lt;/h2&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;/*
end
2
1
0
c
b
a
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://play.golang.org/p/sJgplht4L6h&quot;&gt;# Go Playgound&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注意 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{}&lt;/code&gt; 中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt; (包括 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt; 中) 均会在函数返回时执行, 而不是出当前&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;作用域&lt;/code&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 21 Dec 2020 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2020/12/21/go-pitfalls-and-patterns.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2020/12/21/go-pitfalls-and-patterns.html</guid>
        
        
      </item>
    
      <item>
        <title>使用Bazel编译CGO项目 (Golang + V8)</title>
        <description>&lt;h1 id=&quot;why&quot;&gt;Why&lt;/h1&gt;
&lt;p&gt;最近在写一个小项目, 需要在 Go 执行 JS. 看过了otto, goja 甚至 go-ducktype. 但是出于性能角度的考虑, 还是想用 V8.&lt;/p&gt;

&lt;p&gt;社区有不少 Golang +V8 项目. 但是绝大部分早已停止了维护. 最终看下来 只有 &lt;a href=&quot;https://github.com/augustoroman/v8&quot;&gt;augustoroman/v8&lt;/a&gt; 这个 V8 的 Go wrapper 和 Node.js 的作者 Ry Dahl 的&lt;a href=&quot;https://github.com/ry/v8worker2&quot;&gt;v8worker2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;其中 &lt;a href=&quot;https://github.com/ry/v8worker2&quot;&gt;v8worker2&lt;/a&gt;. 并未暴露 V8 的API, 而是在其基础上包装了一个基于 ArrayBuffer &amp;lt;-&amp;gt; []byte 的接口, 而且也停止了维护. 而 &lt;a href=&quot;https://github.com/augustoroman/v8&quot;&gt;augustoroman/v8&lt;/a&gt; 却不能直接 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go get&lt;/code&gt; 使用, 与 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GO111MODULE&lt;/code&gt; 不兼容等问题.&lt;/p&gt;

&lt;p&gt;于是便有了 &lt;a href=&quot;https://github.com/joesonw/js8&quot;&gt;joesonw/js8&lt;/a&gt; 这个库. 在把已经编译好的 V8 文件打包进来, 并加上对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bazel&lt;/code&gt; 的支持.&lt;/p&gt;

&lt;h1 id=&quot;what&quot;&gt;What&lt;/h1&gt;

&lt;p&gt;当然了, 其中涉及到的代码其实很少, 先贴出来, 再分析.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WORKSPACE&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bazel&quot;&gt;workspace(
    name = &quot;js8&quot;,
)

load(&quot;@bazel_tools//tools/build_defs/repo:http.bzl&quot;, &quot;http_archive&quot;)
load(&quot;@bazel_tools//tools/build_defs/repo:git.bzl&quot;, &quot;git_repository&quot;, &quot;new_git_repository&quot;)

http_archive(
    name = &quot;io_bazel_rules_go&quot;,
    urls = [
        &quot;https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/rules_go/releases/download/0.19.4/rules_go-0.19.4.tar.gz&quot;,
        &quot;https://github.com/bazelbuild/rules_go/releases/download/0.19.4/rules_go-0.19.4.tar.gz&quot;,
    ],
    sha256 = &quot;ae8c36ff6e565f674c7a3692d6a9ea1096e4c1ade497272c2108a810fb39acd2&quot;,
)

load(&quot;@io_bazel_rules_go//go:deps.bzl&quot;, &quot;go_rules_dependencies&quot;, &quot;go_register_toolchains&quot;)
go_rules_dependencies()
go_register_toolchains()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUILD&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bazel&quot;&gt;package(default_visibility = [&quot;//visibility:public&quot;])
load(&quot;@io_bazel_rules_go//go:def.bzl&quot;, &quot;go_library&quot;, &quot;go_test&quot;)
load(&quot;@rules_cc//cc:defs.bzl&quot;, &quot;cc_library&quot;)

cc_library(
    name = &quot;libv8&quot;,
    srcs = select({
        &quot;@io_bazel_rules_go//go/platform:darwin&quot;: glob([&quot;libv8-darwin/libv8/*.a&quot;]),
        &quot;@io_bazel_rules_go//go/platform:linux&quot;: glob([&quot;libv8-linux/libv8/*.a&quot;]),
        &quot;//conditions:default&quot;: [],
    }),
    hdrs = select({
       &quot;@io_bazel_rules_go//go/platform:darwin&quot;: glob([&quot;libv8-darwin/include/*.h&quot;, &quot;libv8-darwin/include/libplatform/*.h&quot;]),
       &quot;@io_bazel_rules_go//go/platform:linux&quot;: glob([&quot;libv8-linux/include/*.h&quot;, &quot;libv8-linux/include/libplatform/*.h&quot;]),
       &quot;//conditions:default&quot;: [],
   }),
)

go_library(
    name = &quot;go_default_library&quot;,
    srcs = [
        &quot;doc.go&quot;,
        &quot;kind.go&quot;,
        &quot;v8_c_bridge.cc&quot;,
        &quot;v8_c_bridge.h&quot;,
        &quot;v8_create_darwin.go&quot;,
        &quot;v8_create_linux.go&quot;,
        &quot;v8_darwin.go&quot;,
        &quot;v8_linux.go&quot;,
    ] + select({
        &quot;@io_bazel_rules_go//go/platform:darwin&quot;: glob([&quot;libv8-darwin/include/*.h&quot;, &quot;libv8-darwin/include/libplatform/*.h&quot;]),
        &quot;@io_bazel_rules_go//go/platform:linux&quot;: glob([&quot;libv8-linux/include/*.h&quot;, &quot;libv8-linux/include/libplatform/*.h&quot;]),
        &quot;//conditions:default&quot;: [],
    }),
    cdeps = [&quot;:libv8&quot;],
    cgo = True,
    importpath = &quot;github.com/joesonw/js8&quot;,
)

go_test(
    name = &quot;go_default_test&quot;,
    srcs = [
        &quot;benchmarks_test.go&quot;,
        &quot;example_bind_test.go&quot;,
        &quot;examples_test.go&quot;,
        &quot;kind_test.go&quot;,
        &quot;v8_test.go&quot;,
    ],
    embed = [&quot;:go_default_library&quot;],
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;#HOW&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WORKSPACE&lt;/code&gt; 不用讲了, 基础操作, 加载 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules_go&lt;/code&gt; 并初始化相关 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dependencies&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;为什么不在要使用的项目里面 直接用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gazelle&lt;/code&gt; 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_repository&lt;/code&gt; 来使用呢? 一开始也是这样想的, 但是发现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_repository&lt;/code&gt; 产出的规则对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgo&lt;/code&gt; 的支持不太好, 其中使用到的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copts&lt;/code&gt; , 而这个项目是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c++&lt;/code&gt; 的, 可以从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-std=c++11&lt;/code&gt; 这看出, 这样就导致了编译失败.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bazel&quot;&gt;copts = select({
    &quot;@io_bazel_rules_go//go/platform:darwin&quot;: [
        &quot;-I. -Ilibv8-darwin/include -fno-rtti -fpic -std=c++11&quot;,
        &quot;-Ilibv8-darwin -Ilibv8-darwin/include -fno-rtti -fpic -std=c++11&quot;,
    ],
    &quot;@io_bazel_rules_go//go/platform:linux&quot;: [
        &quot;-I. -Ilibv8-linux/include -fno-rtti -fpic -std=c++11&quot;,
    ],
    &quot;//conditions:default&quot;: [],
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终的解决方案是通过将 V8 部分先封装成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cc_library&lt;/code&gt;, 然后 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_library&lt;/code&gt; 再将其作为依赖引入, 这样就只需要包含 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgo&lt;/code&gt; 部分代码用到的头文件包含进来即可.&lt;/p&gt;

&lt;p&gt;#结语&lt;/p&gt;

&lt;p&gt;可以看到最终的解决方案并不复杂, 但是提供了一个区别与普通 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_library&lt;/code&gt; 的解决方案,  在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgo&lt;/code&gt; 项目里面不一定要一股脑的都塞到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go_library&lt;/code&gt; 里.&lt;/p&gt;
</description>
        <pubDate>Sat, 19 Oct 2019 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2019/10/19/bazel-cgo-golang-v8.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2019/10/19/bazel-cgo-golang-v8.html</guid>
        
        
      </item>
    
      <item>
        <title>一个用于前端api请求缓存的npm库</title>
        <description>&lt;p&gt;介绍一个新开源的一个小项目. 用来在前端项目上, 缓存请求结果在内存中. 适用于比较大型的应用, 存在多个地方可能重复请求同一个ID, 或者批量中会包含重复的ID. 为了节省资源以及时间, 写了这么一个小小的工具.&lt;/p&gt;

&lt;h2 id=&quot;安装&quot;&gt;安装&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;npm i bulkcache&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;实例&quot;&gt;实例&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import { Cache, FilterFunction } from &apos;bulkcache&apos;;

interface User {
    id: number;
    name: string;
    email: string;
}

function idGetter(user: User) {
    return user.id;
}

async function fetch(id: number) {
    return await YourAPI.fetchUser(id);
}

async function bulkFetch(ids: number[]) {
    return await YourAPI.fetchALotOfUsers(ids);
}

function allIWantIsANameFilter(user: User) {
    return user.name;
}

const cache = new Cache(idGetter, fetch, bulkFetch);
cache.addFilter(FilterFunction(allIWantIsANameFilter));

console.log(await cache.fetch(1)); 
console.log(await cache.fetch(1)); 

cache.fetch(2)
console.log(await cache.fetch(2));

console.log(await cache.batchGet([1, 2]))
console.log(await cache.batchGet([1, 2, 3, 4]))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;先来看看初始化的代码:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function idGetter(user: User) {
    return user.id;
}

async function fetch(id: number) {
    return await YourAPI.fetchUser(id);
}

async function bulkFetch(ids: number[]) {
    return await YourAPI.fetchALotOfUsers(ids);
}

function allIWantIsANameFilter(user: User) {
    return user.name;
}

const cache = new Cache(idGetter, fetch, bulkFetch);
cache.addFilter(FilterFunction(allIWantIsANameFilter));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;idGetter&lt;/code&gt;是为了给每条记录返回一个去重的值, 一般来说就是id.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bulkcache&lt;/code&gt;都是调用的部分, 可以是api, 也可以是任意请求, 只要返回合适的数据. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;是用于按id获取一条数据, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bulkfetch&lt;/code&gt;是用于批量获取, 也可以传空, 这样就会适用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt;来一条条的拿批量的数据&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allIWantIsANameFilter&lt;/code&gt;是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Filter&lt;/code&gt;. 这里示例说我们只要取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.name&lt;/code&gt;, 而不需要整个所有的数据.&lt;/p&gt;

&lt;p&gt;然后就是使用的部分了.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;await cache.fetch(1); 
await cache.fetch(1);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这里只会产生一次网络请求, 因为第二次请求的时候, 数据已经在内存里了.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cache.fetch(2)
console.log(await cache.fetch(2));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这里我们并没有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await&lt;/code&gt;第一次的调用, 也就是第二次调用时, 第一次的结果还没返回, 但是还是只会产生一次网络请求.&lt;/p&gt;

&lt;p&gt;原来是, 在第一次调用时, 已经创建了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q.Deferred&lt;/code&gt;. 第二次调用时, 发现了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q.Deferred&lt;/code&gt;, 就会直接等待第一次调用的请求完成, 然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolve&lt;/code&gt;这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;defer&lt;/code&gt;之后, 第二次调用也会拿到数据了.&lt;/p&gt;

&lt;p&gt;最后&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;console.log(await cache.batchGet([1, 2]))
console.log(await cache.batchGet([1, 2, 3, 4]))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这里我们使用到了批量请求, 因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;已经存在于内存中了, 所以并不会产生任何网络请求, 直接返回.&lt;/p&gt;

&lt;p&gt;第二次调用会产生一个批量网络请求, 但是请求中只会包含&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4&lt;/code&gt;, 因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt;会直接从内存中拿出来一起返回给调用者&lt;/p&gt;

&lt;h2 id=&quot;结语&quot;&gt;结语&lt;/h2&gt;
&lt;p&gt;这是最近游戏项目中算是比较常用的一个小功能, 这里拿出来分享给大家, 希望工作中能更轻快更好的完成需求.&lt;/p&gt;

&lt;p&gt;最近快过年了, 放假了有时间了, 整理一些比较小巧好用的小包出来.&lt;/p&gt;

&lt;p&gt;最后, 欢迎PR. 当然, 也欢迎Issue!&lt;/p&gt;

&lt;p&gt;Till next time.&lt;/p&gt;
</description>
        <pubDate>Sat, 12 Jan 2019 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2019/01/12/bulkcache-intro.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2019/01/12/bulkcache-intro.html</guid>
        
        
      </item>
    
      <item>
        <title>工作随笔: 白鹭Egret5.1 实用插件开发指南</title>
        <description>&lt;p&gt;白鹭引擎在升级到5.1的同时, 迎来了一个重大功能的更新. 引擎和编译彻底分开, 同时, 对于编译功能引入的插件机制. 极大的方便了定制工作流.&lt;/p&gt;

&lt;h2 id=&quot;1-插件机制&quot;&gt;1 插件机制&lt;/h2&gt;

&lt;p&gt;首先来介绍一下5.1之后的机制.&lt;/p&gt;

&lt;p&gt;当使用新创建一个基于5.1的项目之后. 会发现与之前不同, 会多出一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scripts&lt;/code&gt;文件夹. 这个文件夹里面就是编译相关的类型, 其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.ts&lt;/code&gt;便是编译的入口文件了, 类似于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;由于自动生成的里面有很多 ‘无效’ 的代码, 所以我们先来精简一下配置(关键代码).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const config: ResourceManagerConfig = {
    configPath: &apos;&apos;,
    resourceRoot: () =&amp;gt; &apos;resource&apos;,
    buildConfig: (params) =&amp;gt; {
        const { target, command, projectName, version } = params;
        if (command === &apos;build&apos;) {
            const outputDir = &apos;.&apos;;
            return {
                outputDir,
                commands: [
                    new ExmlPlugin(&apos;debug&apos;), // 非 EUI 项目关闭此设置
                    new IncrementCompilePlugin(),
                ],
            };
        } else if (command === &apos;publish&apos;) {
            const outputDir = `bin-release/`;
            rimraf(path.join(params.projectRoot, outputDir));
            return {
                outputDir,
                commands: [
                    new CompilePlugin({ libraryType: &apos;release&apos; }),
                    new ExmlPlugin(&apos;commonjs&apos;), // 非 EUI 项目关闭此设置
                    new UglifyPlugin([
                        {
                            sources: [&apos;main.js&apos;, &apos;config.js&apos;],
                            target: &apos;main.min.js&apos;,
                        },
                    ]),
                ],
            };
        } else {
            throw `unknown command : ${params.command}`;
        }
    },
    mergeSelector: (path) =&amp;gt; null,
    typeSelector: (path) =&amp;gt; null,
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中保留最核心的几个插件&lt;/p&gt;

&lt;p&gt;对于开发时的build, 只需要保留&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IncrementCompilePlugin&lt;/code&gt;(增量更新), 和ExmlPlugin(如果你用到了eui的话).&lt;/p&gt;

&lt;p&gt;发布的时候删除了release文件夹(并没有采取默认的按增量的版本号来发布, 因为是用服务器上做CI的时候发布的, 你也可以不修改).&lt;/p&gt;

&lt;h2 id=&quot;2-插件开发&quot;&gt;2 插件开发&lt;/h2&gt;

&lt;p&gt;首先所有的插件都是要实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins.Command&lt;/code&gt;这个接口. 按照提供的顺序执行.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugins.Command&lt;/code&gt;其包含两个函数需要实现&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFile(file: plugins.File): Promise&amp;lt;plugins.File&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;和&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFinish(context: plugins.CommandContext): Promise&amp;lt;void&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFile&lt;/code&gt;会对每个文件执行一遍(在前一个插件执行完后剩下的文件), 如果返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;的话, 表示不生成此文件(删除此文件), 否则返回file. 可以通过修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file.path&lt;/code&gt;改变其路径和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file.content&lt;/code&gt;改变其内容&lt;/p&gt;

&lt;p&gt;下一节我们来看一个简单的插件开发&lt;/p&gt;

&lt;h2 id=&quot;3-文件hash版本号&quot;&gt;3 文件hash版本号&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default class VersionedManifest implements plugins.Command {
    private manifestGameFiles: string[] = [];
    private manifestInitialFiles: string[] = [];

    async onFile(file: plugins.File): Promise&amp;lt;plugins.File&amp;gt; {
        if (file.extname === &apos;.js&apos;) {
            const hash = crypto.createHash(&apos;md5&apos;).update(file.contents).digest(&apos;hex&apos;);
            file.path = file.path.substr(0, file.path.length - 3) + &apos;.&apos; + hash + &apos;.js&apos;;
            if (file.basename.indexOf(&apos;main.min&apos;) &amp;gt;= 0) {
                this.manifestGameFiles.push(file.relative);
            } else {
                this.manifestInitialFiles.push(file.relative);
            }
        }
        return file;
    }

    async onFinish(context: plugins.CommandContext): Promise&amp;lt;void&amp;gt; {
        const manifest = JSON.stringify({
            initial: this.manifestInitialFiles,
            game: this.manifestGameFiles,
        });
        const hash = crypto.createHash(&apos;md5&apos;).update(manifest).digest(&apos;hex&apos;);
        await context.createFile(`manifest.${hash}.json`, new Buffer(manifest));
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;首先, 我们在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFile&lt;/code&gt;的时候判断. 如果是js文件, 则hash其内容产出一个文件签名, 并添加到文件名中, 并缓存文件名.&lt;/p&gt;

&lt;p&gt;最后, 在所有文件处理完成后, 将带有签名的js文件生成一个带版本号的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;这样, 生成的文件就可以在CDN上做&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200 缓存&lt;/code&gt;了.&lt;/p&gt;

&lt;h2 id=&quot;4-资源版本控制&quot;&gt;4. 资源版本控制&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function hash(content: string | Buffer): string {
    return crypto.createHash(&apos;md5&apos;).update(content).digest(&apos;hex&apos;);
}

export default class VersionedResource implements plugins.Command {
    private defaultResJson: Buffer;
    private cachedResource: {[key: string]: Buffer} = {};

    async onFile(file: plugins.File): Promise&amp;lt;plugins.File&amp;gt; {
       if (file.basename.indexOf(&apos;default.res.json&apos;) &amp;gt;= 0) {
            this.defaultResJson = file.contents;
            return null;
        } else if (file.relative.startsWith(&apos;resource&apos;) &amp;amp;&amp;amp; !file.relative.endsWith(&apos;.js&apos;)) {
            this.cachedResource[file.relative.substr(9)] = file.contents;
            return null;
        }

        return file;
    }

    async onFinish(context: plugins.CommandContext): Promise&amp;lt;void&amp;gt; {
        const json = JSON.parse(this.defaultResJson.toString());
        const resources = [];
        for (const resource of json.resources) {
            switch (resource.type) {
            case &apos;sheet&apos;: {
                let jsonPath = resource.url;
                const json = JSON.parse(this.cachedResource[jsonPath].toString());
                let pngPath = path.relative(
                    context.buildConfig.projectRoot,
                    path.resolve(jsonPath, &apos;../&apos;, json.file),
                );
                const pngBuffer = this.cachedResource[pngPath];

                const pngHash = hash(pngBuffer);
                pngPath = pngPath.substr(0, pngPath.length - 4) + &apos;.&apos; + pngHash + &apos;.png&apos;;

                json.file = pngPath.split(&apos;/&apos;).slice(1).join(&apos;/&apos;);
                const jsonBuffer = new Buffer(JSON.stringify(json));
                const jsonHash = hash(jsonBuffer);
                jsonPath = jsonPath.substr(0, jsonPath.length - 5) + &apos;.&apos; + jsonHash + &apos;.json&apos;;

                context.createFile(path.join(&apos;resource&apos;, pngPath), pngBuffer);
                context.createFile(path.join(&apos;resource&apos;, jsonPath), new Buffer(jsonBuffer));
                resource.url = jsonPath;
                resources.push(resource);
                break;
            }
            case &apos;sound&apos;:
            case &apos;json&apos;:
            case &apos;image&apos;: {
                const file = this.cachedResource[resource.url];
                const h = hash(file);
                const ext = path.extname(resource.url);
                resource.url =
                    resource.url.substr(0, resource.url.length - ext.length) + &apos;.&apos; + h + ext;
                context.createFile(path.join(&apos;resource&apos;, resource.url), file);
                resources.push(resource);
                break;
            }
            default:
                throw new Error(`resource type: ${resource.type} is not supported`);
            }
        }

        json.resources = resources;
        const buffer = new Buffer(JSON.stringify(json));
        const h = hash(buffer);
        context.createFile(path.join(&apos;resource&apos;, &apos;default.res.&apos; + h + &apos;.json&apos;), buffer);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个插件最主要的功能就是把所用到的资源也给加上一个版本号.&lt;/p&gt;

&lt;p&gt;但是和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt;文件稍微有些区别. 我们得首先获取到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;里面的内容, 根据其用到的文件来进行哈希.&lt;/p&gt;

&lt;p&gt;在处理资源的时候, 对于普通资源. 只需要简单的读取其文件内容, 哈希, 写回即可.&lt;/p&gt;

&lt;p&gt;但是针对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;(SpriteSheet)的时候, 情况稍微有一点特殊. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheet&lt;/code&gt;类型的资源所指向的是一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;文件, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;文件再指向雪碧图的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;png&lt;/code&gt;. 所以需要先读取&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;, 修改其指向的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;png&lt;/code&gt;的文件后, 再整体哈希, 写回.&lt;/p&gt;

&lt;h2 id=&quot;5-合并纹理图集&quot;&gt;5 合并纹理图集&lt;/h2&gt;

&lt;p&gt;在h5游戏中, 为了更好更快的加载资源. 我们通常会将一起用到的图片资源资源打包成一个雪碧图.&lt;/p&gt;

&lt;p&gt;白鹭提供了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Texture Merger&lt;/code&gt;来生成雪碧图, 并用自带的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;game&lt;/code&gt;库来解析. 但是在实际的开发体验中, 这样的体验并不流畅. 每次美工修改一点点细节都需要重新打包资源, 替代.&lt;/p&gt;

&lt;p&gt;工作中, 我们开发了这样一套插件. 所有的资源使用原生态(不打包). 但是在开发中, 在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;中的引用时, 命名会有规则.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;例如, 原本要打包到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui&lt;/code&gt;这个包下面的资源&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;btn_close&lt;/code&gt;, 直接命名为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui.btn_close&lt;/code&gt;. 事后打包时, 会将所有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui.&lt;/code&gt;开头的资源打包在一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SpiteSheet&lt;/code&gt;中&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这样我们写一个helper funciton直接&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getAsset(&apos;ui.btn_close&apos;)&lt;/code&gt;, 开发环境中会直接取这个资源, 而生产环境中去&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui&lt;/code&gt;这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SpiteSheet&lt;/code&gt;中取.&lt;/p&gt;

&lt;h3 id=&quot;51-合并纹理图集插件&quot;&gt;5.1 合并纹理图集插件&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export default class MergeTexture implements plugins.Command {
    private imageBufferMap: Map&amp;lt;string, Buffer&amp;gt;;
    private resFile: Buffer;

    constructor(private output: string) {
        this.imageBufferMap = new Map&amp;lt;string, Buffer&amp;gt;();
    }

    async onFile(file: plugins.File): Promise&amp;lt;plugins.File&amp;gt; {
        if (file.extname === &apos;.png&apos;) {
            this.imageBufferMap.set(file.relative, file.contents);
            return null;
        } else if (file.basename.indexOf(&apos;default.res.json&apos;) &amp;gt;= 0) {
            this.resFile = file.contents;
            return null;
        }
        return file;
    }

    async onFinish(pluginContext: plugins.CommandContext): Promise&amp;lt;void&amp;gt; {
        const components: {[key: string]: any} = {};
        const json = JSON.parse(this.resFile.toString());
        const resources: any[] = [];
        for (const res of json.resources) {
            if (res.name.indexOf(&apos;.&apos;) &amp;gt;= 0) {
                const url = path.join(&apos;resource&apos;, res.url);
                const image = this.imageBufferMap.get(url);
                const group = res.name.split(&apos;.&apos;)[0];
                const name = res.name.split(&apos;.&apos;).slice(1).join(&apos;.&apos;);
                components[group] = components[group] || [];
                components[group].push({
                    name,
                    image,
                });
            } else {
                resources.push(res);
            }
        }

        for (const group in components) {
            const resultSet = await merge(components[group]);
            await pluginContext.createFile(
                path.join(&apos;resource/&apos;, this.output, group + &apos;.png&apos;),
                resultSet.image,
            );
            await pluginContext.createFile(
                path.join(&apos;resource/&apos;,this.output, group + &apos;.json&apos;),
                new Buffer(JSON.stringify({
                    file: group + &apos;.png&apos;,
                    frames: resultSet.frames,
                })),
            );
            const subkeys = [];
            for (const key in resultSet.frames) {
                subkeys.push(key);
            }
            resources.push({
                subkeys,
                url: path.join(this.output,  group + &apos;.json&apos;),
                type: &apos;sheet&apos;,
                name: group,
            });

            json.groups = json.groups.map((g) =&amp;gt; {
                let keys = g.keys.split(&apos;,&apos;);
                const length = keys.length;
                keys = keys.filter(key =&amp;gt; !key.startsWith(group));
                if (length !== keys.length) {
                    keys.push(group);
                }
                g.keys = keys.join(&apos;,&apos;);
                return g;
            });

        }

        json.resources = resources;
        const res = JSON.stringify(json, null, 2);
        await pluginContext.createFile(&apos;resource/default.res.json&apos;, new Buffer(res));

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个插件涉及到的步骤比较多, 我们分开来讲.&lt;/p&gt;

&lt;p&gt;首先在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFile&lt;/code&gt;里&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (file.extname === &apos;.png&apos;) {
    this.imageBufferMap.set(file.relative, file.contents);
    return null;
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们先把所有的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.png&lt;/code&gt;文件内容拦截下来(因为后面要生成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;, &lt;em&gt;散装&lt;/em&gt;的图片用不到了), 并将其内容存下来.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;} else if (file.basename.indexOf(&apos;default.res.json&apos;) &amp;gt;= 0) {
        this.resFile = file.contents;
        return null;
    }
    return file;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后我们拦截下来&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;里面的内容&lt;/p&gt;

&lt;p&gt;在所有文件遍历过后, 在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFinish&lt;/code&gt;里面.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const components: {[key: string]: any} = {};
const json = JSON.parse(this.resFile.toString());
const resources: any[] = [];
for (const res of json.resources) {
    if (res.name.indexOf(&apos;.&apos;) &amp;gt;= 0) {
        const url = path.join(&apos;resource&apos;, res.url);
        const image = this.imageBufferMap.get(url);
        const group = res.name.split(&apos;.&apos;)[0];
        const name = res.name.split(&apos;.&apos;).slice(1).join(&apos;.&apos;);
        components[group] = components[group] || [];
        components[group].push({
            name,
            image,
        });
    } else {
        resources.push(res);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们先根据&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;里面引用的资源按前缀名(如果有)来分组, 并取得其内容(在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onFile&lt;/code&gt;里缓存的)&lt;/p&gt;

&lt;p&gt;然后再在对每一个组的循环里面,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const resultSet = await merge(components[group]);
await pluginContext.createFile(
    path.join(&apos;resource/&apos;, this.output, group + &apos;.png&apos;),
    resultSet.image,
);
await pluginContext.createFile(
    path.join(&apos;resource/&apos;,this.output, group + &apos;.json&apos;),
    new Buffer(JSON.stringify({
        file: group + &apos;.png&apos;,
        frames: resultSet.frames,
    })),
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述代码将每一个组里面的图片进行合并(合并的逻辑将在下面讲到), 并将这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;(对应一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt;和一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;png&lt;/code&gt;)写入到硬盘上.&lt;/p&gt;

&lt;p&gt;然后下面的一步, 将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;里面对于’散件’的引用移除, 替换为对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;的引用.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const subkeys = [];
for (const key in resultSet.frames) {
    subkeys.push(key);
}
resources.push({
    subkeys,
    url: path.join(this.output,  group + &apos;.json&apos;),
    type: &apos;sheet&apos;,
    name: group,
});

json.groups = json.groups.map((g) =&amp;gt; {
    let keys = g.keys.split(&apos;,&apos;);
    const length = keys.length;
    keys = keys.filter(key =&amp;gt; !key.startsWith(group));
    if (length !== keys.length) {
        keys.push(group);
    }
    g.keys = keys.join(&apos;,&apos;);
    return g;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.res.json&lt;/code&gt;的内容写入磁盘&lt;/p&gt;

&lt;h3 id=&quot;52-纹理集合并逻辑&quot;&gt;5.2 纹理集合并逻辑&lt;/h3&gt;

&lt;p&gt;上面的插件用到了一个函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;merge&lt;/code&gt;, 用来将&lt;em&gt;散装&lt;/em&gt;的图片打包成一个纹理集.&lt;/p&gt;

&lt;p&gt;先上代码:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&apos;use strict&apos;;

const jimp = require(&apos;jimp&apos;);
const layout = require(&apos;layout&apos;);
const co = require(&apos;co&apos;);

exports.default = co.wrap(function* (items) {
    const resultSet = {
        frames: {},
        image: null,
    };

    const layer = layout(&apos;binary-tree&apos;);
    for (const item of items) {
        const image = yield cb =&amp;gt; jimp.read(item.image, cb);

        const sourceW = image.bitmap.width;
        const sourceH = image.bitmap.height;

        const xSpaces = Array(sourceW).fill(true);
        const ySpaces = Array(sourceH).fill(true);

        yield cb =&amp;gt; image.scan(0, 0, sourceW, sourceH, (x, y, idx) =&amp;gt; {
            const sum = image.bitmap.data[idx] +
                        image.bitmap.data[idx + 1] +
                        image.bitmap.data[idx + 2] +
                        image.bitmap.data[idx + 3];
            if (sum !== 0) {
                xSpaces[x] = false;
                ySpaces[y] = false;
            }
        }, cb);

        const offX = xSpaces.indexOf(false);
        const offY = ySpaces.indexOf(false);


        const frame = {
            name: item.name,
            image,
            offX,
            offY,
            sourceW,
            sourceH,
            w: xSpaces.lastIndexOf(false) + 1 - offX,
            h: ySpaces.lastIndexOf(false) + 1 - offY,
        };

        layer.addItem({
            meta: frame,
            width: frame.w,
            height: frame.h,
        });
    }

    const info = layer.export();
    resultSet.image = yield cb =&amp;gt; new jimp(info.width, info.height, cb);

    for (const block of info.items) {
        const frame = block.meta
        const name = frame.name;
        const image = frame.image;

        delete frame.name;
        delete frame.image;

        frame.x = block.x;
        frame.y = block.y;

        resultSet.frames[name] = frame;

        yield cb =&amp;gt; image.crop(frame.offX, frame.offY, frame.w, frame.h, cb);
        yield cb =&amp;gt; resultSet.image.composite(image, frame.x, frame.y, cb);
    }

    resultSet.image = yield cb =&amp;gt; resultSet.image.getBuffer(jimp.MIME_PNG, cb);

    return resultSet;
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jimp&lt;/code&gt;这个包是一个&lt;strong&gt;纯js&lt;/strong&gt;的图片操作库, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout&lt;/code&gt;是一个图片打包排序的库, 以空间利用率最大的方式将所有&lt;em&gt;散装&lt;/em&gt;图片组合到一个图片上.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const sourceW = image.bitmap.width;
const sourceH = image.bitmap.height;

const xSpaces = Array(sourceW).fill(true);
const ySpaces = Array(sourceH).fill(true);

yield cb =&amp;gt; image.scan(0, 0, sourceW, sourceH, (x, y, idx) =&amp;gt; {
    const sum = image.bitmap.data[idx] +
                image.bitmap.data[idx + 1] +
                image.bitmap.data[idx + 2] +
                image.bitmap.data[idx + 3];
    if (sum !== 0) {
        xSpaces[x] = false;
        ySpaces[y] = false;
    }
}, cb);

const offX = xSpaces.indexOf(false);
const offY = ySpaces.indexOf(false);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一段代码是为了将图片外圈的白边(白色且透明)给识别出来. 其中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offX&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;offY&lt;/code&gt;表示有效内容(非白边)开始的位置.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;frame&lt;/code&gt;的格式为白鹭的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Texture Merger&lt;/code&gt;生成的格式, 为了能无缝使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;game&lt;/code&gt;库加载&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;, 变沿用了这套格式.&lt;/p&gt;

&lt;p&gt;将计算好的数据添加到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layer&lt;/code&gt;上, export出来后的数据便是排好位置的坐标值了.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yield cb =&amp;gt; image.crop(frame.offX, frame.offY, frame.w, frame.h, cb);&lt;/code&gt;这样将原始的&lt;em&gt;散装&lt;/em&gt;图片按照之前计算的白边距离给裁剪掉(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h&lt;/code&gt;是去掉白边后的宽高)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yield cb =&amp;gt; resultSet.image.composite(image, frame.x, frame.y, cb);&lt;/code&gt;将&lt;em&gt;散装&lt;/em&gt;图片内容复制到最后的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;纹理集&lt;/code&gt;大图上&lt;/p&gt;

&lt;p&gt;这样就大工告成啦.&lt;/p&gt;

&lt;h2 id=&quot;6-总结&quot;&gt;6 总结&lt;/h2&gt;

&lt;p&gt;当然工作中还开发了一些其他的插件, 限于篇幅和文章重心的缘由就没有列出来了. 文章中提到的几个插件算是比较核心且必备的.&lt;/p&gt;

&lt;p&gt;在熟悉开发插件的原理以及流程后, 你也可以开发出适合自己的工作流程的插件啦.&lt;/p&gt;

&lt;h2 id=&quot;打个广告&quot;&gt;打个广告&lt;/h2&gt;

&lt;p&gt;下面是自己弄的个小公众号, 用来发发文章, 感想什么的.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2018/3/28/1626854a84cd5131?w=258&amp;amp;h=258&amp;amp;f=jpeg&amp;amp;s=26668&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 14 Apr 2018 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2018/04/14/egret-plugins.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2018/04/14/egret-plugins.html</guid>
        
        
      </item>
    
      <item>
        <title>记一次公司阿里云内网搭建</title>
        <description>&lt;p&gt;刚刚加入一个创业公司, 开始负责技术方面的事情. 首先呢, 肯定是先搭建服务器和内网环境. 在这里就过一遍阿里云搭建内网的一个过程. 作为一个自己的记录, 也给有同样需求的朋友们一个分享.&lt;/p&gt;

&lt;p&gt;首先是阿里云机器的配置:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;首先创建一个专有网络&lt;/li&gt;
  &lt;li&gt;创建一个交换机(子网)&lt;/li&gt;
  &lt;li&gt;然后前往&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VPC路由器&lt;/code&gt;界面, 添加一条路由, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;目标网段&lt;/code&gt;填写&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0.0/0&lt;/code&gt;, 表示此网络下所有机器, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;下一跳&lt;/code&gt;选择你的堡垒机, 这样内网的出口流量都会导到堡垒机上&lt;/li&gt;
  &lt;li&gt;登入堡垒机, 运行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iptables -t nat -A POSTROUTING -s 10.0.0.0/8 -o eth0 -j MASQUERADE&lt;/code&gt;, 其中 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.0.0.0/8&lt;/code&gt;根据实际情况填入你的交换机网段, 这样内网流量都使用了堡垒机ip&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置内网dns解析(堡垒机)&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;centos&lt;/code&gt;为例, 安装dnsmasq, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install -y dnsmasq&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;修改&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/dnsmasq.conf&lt;/code&gt;, 在文件末尾添加如下内容
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 所有没有.号的域名(plain names)都不会向上游DNS Server转发，只查询hosts文件
domain-needed
# 所有保留IP地址段内的反向查询都不会向上游DNS Server转发，只查询hosts文件
bogus-priv
# 不要读取/etc/resolver中的DNS Server的配置
no-resolv
# 不要poll /etc/resolver文件的更新
no-poll
# 下面这两个配置我们的上游DNS服务器
server=8.8.8.8
server=8.8.4.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt;中添加你需要的域名解析&lt;/li&gt;
  &lt;li&gt;在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/resolv.conf&lt;/code&gt;中, 于阿里云内网nameserver前面添加本机ip,如: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nameserver 10.0.0.1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;重启dnsmasq, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl restart dnsmasq&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;配置openvpn&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;一键安装配置openvpn, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget https://git.io/vpn -O openvpn-install.sh &amp;amp;&amp;amp; bash openvpn-install.sh&lt;/code&gt;, 注意过程中, 需要填写公网ip, 即为弹性公网ip. 其余均可为默认&lt;/li&gt;
  &lt;li&gt;修改openvpn配置
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/openvpn/server.conf&lt;/code&gt;, 注销掉&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push &quot;dhcp-option DNS xxx.xxx.xxx.xxx&quot;&lt;/code&gt;, 添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push &quot;dhcp-option DNS 10.0.0.1&quot;&lt;/code&gt;, 使用本机(堡垒机)作为dns解析.&lt;/li&gt;
      &lt;li&gt;注释掉&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push &quot;redirect-gateway def1 bypass-dhcp&quot;&lt;/code&gt;, 这样客户端才可连接外网.&lt;/li&gt;
      &lt;li&gt;添加`push “route 10.0.0.0 255.255.255.0”, 添加可以被访问到的内网网段&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iptables -t nat -A POSTROUTING -s 10.8.0.0/16 -o eth0 -j MASQUERADE&lt;/code&gt;, 这样客户端才能访问到内网&lt;/li&gt;
  &lt;li&gt;重启openvpn, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl restart openvpn@server&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样整个环境就配置好了, 客户端osx我选择的是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tunnelblick&lt;/code&gt;, 使用刚才安装openvpn过程中生成的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.opvn&lt;/code&gt;文件便可导入vpn啦. 连上vpn后便可无缝访问内网/外网, 并可通过自定义dns解析访问公司内网服务.&lt;/p&gt;

&lt;p&gt;最后记得正确的配置阿里云安全组, 堡垒机只需开放&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcp 22&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;udp 1194&lt;/code&gt;端口即可(当然, 要允许所有内网的访问, 否则内网机器也无法通过此机器访问外网了).&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Nov 2017 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2017/11/14/aliyun-vpc.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2017/11/14/aliyun-vpc.html</guid>
        
        
      </item>
    
      <item>
        <title>How to install nvm for all users on NFS</title>
        <description>&lt;p&gt;The environment is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ubuntu 14.04 LTS&lt;/code&gt;&lt;/p&gt;

&lt;h1 id=&quot;nfs-server-side-setup&quot;&gt;NFS Server-side setup&lt;/h1&gt;

&lt;p&gt;Let’s say your NFS shared directory is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/shared/local&lt;/code&gt;, and it maps to local(NFS server) directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;First, we need to clone nvm, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone https://github.com:creationix/nvm.git /usr/local/src/nvm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then let’s make two directories for storing nvm stff.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir /usr/local/nvm&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir /usr/local/node&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And we then create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/profile.d/nvm.sh&lt;/code&gt; on local(NFS server), this allows us to install node binaries.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
export NVM_DIR=/usr/local/nvm
. /usr/local/src/nvm/nvm.sh
export NPM_CONFIG_PREFIX=/usr/local/node
export PATH=&quot;/usr/local/node/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After logging out, and log back in (or simply type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;. /etc/profile.d/nvm.sh&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;we then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm install 0.12&lt;/code&gt; (in this case, we install node v0.12, we can also install other versions)&lt;/p&gt;

&lt;p&gt;Now run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown 755 -R /usr/local/nvm&lt;/code&gt;, this gives nfs client approriate access rights. (Otherwise you will see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Now using node undefined&lt;/code&gt; after running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm use 0.12&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;we then create another file (maps to shared directory.Remember,client do not have nvm installed on their  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Let’s make one file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/nvm.sh&lt;/code&gt; on nfs server&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
export NVM_DIR=/shared/local/nvm
. /shared/local/src/nvm/nvm.sh
export NPM_CONFIG_PREFIX=/shared/local/node
xport PATH=&quot;/shared/local/node/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;#2 NFS Client-side setup&lt;/p&gt;

&lt;p&gt;Now, let’s log into client server (as root)&lt;/p&gt;

&lt;p&gt;Let’s create one file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/profile.d/shared.sh&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
export PATH=&quot;/shared/local/bin:$PATH&quot;
. /shared/local/init.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will execute nvm initation scripts we created at the end of last section and add node binaries locations to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you can log out and log back in, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm&lt;/code&gt; , TADA!!&lt;/p&gt;
</description>
        <pubDate>Tue, 21 Jul 2015 00:00:00 +0000</pubDate>
        <link>https://joeosnw.github.io/2015/07/21/npm-nfs.html</link>
        <guid isPermaLink="true">https://joeosnw.github.io/2015/07/21/npm-nfs.html</guid>
        
        
      </item>
    
  </channel>
</rss>
