<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="https://dwt.life/feed/rss/tag/%E7%BB%9F%E8%AE%A1/">
<title>dwt&#039;s life - 统计</title>
<link>https://dwt.life/tag/%E7%BB%9F%E8%AE%A1/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://dwt.life/archives/241/"/>
<rdf:li resource="https://dwt.life/archives/228/"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://dwt.life/archives/241/">
<title>解决UMAMI统计脚本被广告拦截插件屏蔽的问题</title>
<link>https://dwt.life/archives/241/</link>
<dc:date>2022-04-27T12:58:00+08:00</dc:date>
<description>上一篇文章讲到了如何在本机环境和docker中去安装umami，这一篇讲一下如何防止umami脚本被屏蔽。默认的代码如下：&lt;script async defer data-website-id=&quot;6b43b895-17fb-42d5-aacc-ef0841ca34c5&quot; src=&quot;http://ip:3000/umami.js&quot;&gt;&lt;/script&gt;有两种方法，但是都需要修改umami的脚本名。第一种是通过反代、worker、修改配置实现，第二种是重新混淆js实现，殊途同归。通过umami配置文件修改脚本名该方法需要重启umami来使其生效在上一篇中创建的.env文件中增加一行TRACKER_SCRIPT_NAME=新脚本名称如果是docker的话，需要通过docker ps定位到容器，然后执行docker attach 容器ID进入并找到.env文件进行修改（实际可能并不能进入到终端），或者是创建的时候使用-v参数将volume映射一份到本地目录。修改完成后重启：pm2 restart umami重启后效果如下：但是dashboard中代码仍然显示的umami.js，我们换成自己定义的名称.js即可。原因在其umami/pages/_middleware.js代码中通过process.env.TRACKER_SCRIPT_NAME获取变量从而决定track脚本的名称：function customScriptName(req) {
  const scriptName = process.env.TRACKER_SCRIPT_NAME;

  if (scriptName) {
    const url = req.nextUrl.clone();
    const { pathname } = url;
    const names = scriptName.split(&#039;,&#039;).map(name =&gt; (name + &#039;.js&#039;).trim());

    if (names.find(name =&gt; pathname.endsWith(name))) {
      url.pathname = &#039;/umami.js&#039;;
      return NextResponse.rewrite(url);
    }
  }
}通过反向代理重命名脚本名称我们可以直接添加以下内容到伪静态location /myjs {
    proxy_pass http://127.0.0.1:3000/umami.js;
}然后访问/myjs即可，同理上步。通过cloudflare worker进行http代理此处就不以cloudflare为例了，我们使用百度云加速：worker代码：const ScriptName = &#039;/umami.js&#039;;     //自定义脚本名称
const Endpoint = &#039;/foo/bar&#039;;
const UmamiUrl = &#039;https://uaxk.com&#039;;  //UMAMI服务域名地址。
const corsHeaders = {
    &#039;Access-Control-Allow-Origin&#039;: &#039;*&#039;,
    &#039;Access-Control-Allow-Methods&#039;: &#039;GET,HEAD,POST,OPTIONS&#039;,
    &#039;Access-Control-Max-Age&#039;: &#039;86400&#039;,
};
const ScriptWithoutExtension = ScriptName.replace(&#039;.js&#039;, &#039;&#039;)
addEventListener(&#039;fetch&#039;, event =&gt; {
    event.passThroughOnException();
    event.respondWith(handleRequest(event));
})
async function handleRequest(event) {
    const pathname = new URL(event.request.url).pathname
    const [baseUri, ...extensions] = pathname.split(&#039;.&#039;)
    const clientIP = event.request.headers.get(&quot;CF-Connecting-IP&quot;)
    if (baseUri.endsWith(ScriptWithoutExtension)) {
        return getScript(event, extensions)
    } else if (pathname.endsWith(Endpoint)) {
        return postData(event)
    }
    return new Response(null, {status: 404})
}
async function getScript(event, extensions) {
    let response = await caches.default.match(event.request);
    if (!response) {
        response = await fetch(UmamiUrl +&quot;/umami.js&quot;);
        var js = await response.text();
        js = js.replace(&quot;/api/collect&quot;, Endpoint);
        response = new Response(js, {
            headers: {
                ...response.headers,
                ...corsHeaders,
                &#039;Access-Control-Allow-Headers&#039;: response.headers.get(&#039;Access-Control-Request-Headers&#039;),
            },
        })
        event.waitUntil(caches.default.put(event.request, response.clone()));
    }
    return response;
}
async function postData(event) {
    const request = new Request(event.request);
    request.headers.delete(&#039;cookie&#039;);
    response = await fetch(UmamiUrl +&quot;/api/collect&quot;, request);
    var js = await response.text();
    response = new Response(js, {
        headers: {
            ...response.headers,
            ...corsHeaders,
            &#039;Access-Control-Allow-Headers&#039;: request.headers.get(&#039;Access-Control-Request-Headers&#039;),
        },
    });
    return response;
}由于百度云加速的限制，暂时无法验证是否可以使用，但是可以参考另外一篇博文，里面做了cloudflare的worker演示。混淆js实现隐私防护屏蔽umami等统计对于国内的一些隐私防护插件可以根据umami等统计脚本的代码特征进行识别从而屏蔽。我们可以修改umami脚本后进行混淆，达到不被拦截的效果，当然，前提也是不要用很明显的二级、一级域名告诉别人这是统计用的。原来的统计代码：!function(){&quot;use strict&quot;;var t=function(t,e,n){var a=t;return function(){for(var e=[],i=arguments.length;i--;)e[i]=arguments[i];return n.apply(null,e),a.apply(t,e)}},e=function(){var t=window.doNotTrack,e=window.navigator,n=window.external,a=&quot;msTrackingProtectionEnabled&quot;,i=t||e.doNotTrack||e.msDoNotTrack||n&amp;&amp;a in n&amp;&amp;n[a]();return&quot;1&quot;==i||&quot;yes&quot;===i};!function(n){var a=n.screen,i=a.width,r=a.height,o=n.navigator.language,c=n.location,s=c.hostname,u=c.pathname,l=c.search,d=n.localStorage,f=n.document,v=n.history,p=f.querySelector(&quot;script[data-website-id]&quot;);if(p){var m,g,h=p.getAttribute.bind(p),y=h(&quot;data-website-id&quot;),w=h(&quot;data-host-url&quot;),b=&quot;false&quot;!==h(&quot;data-auto-track&quot;),S=h(&quot;data-do-not-track&quot;),k=&quot;false&quot;!==h(&quot;data-css-events&quot;),E=h(&quot;data-domains&quot;)||&quot;&quot;,N=E.split(&quot;,&quot;).map((function(t){return t.trim()})),T=/^umami--([a-z]+)--([\w]+[\w-]*)$/,q=&quot;[class*=&#039;umami--&#039;]&quot;,A=function(){return d&amp;&amp;d.getItem(&quot;umami.disabled&quot;)||S&amp;&amp;e()||E&amp;&amp;!N.includes(s)},O=w?(m=w)&amp;&amp;m.length&gt;1&amp;&amp;m.endsWith(&quot;/&quot;)?m.slice(0,-1):m:p.src.split(&quot;/&quot;).slice(0,-1).join(&quot;/&quot;),j=i+&quot;x&quot;+r,L={},_=&quot;&quot;+u+l,x=f.referrer,H=function(){return{website:y,hostname:s,screen:j,language:o,url:_}},R=function(t,e){return Object.keys(e).forEach((function(n){t[n]=e[n]})),t},J=function(t,e){A()||function(t,e,n){var a=new XMLHttpRequest;a.open(&quot;POST&quot;,t,!0),a.setRequestHeader(&quot;Content-Type&quot;,&quot;application/json&quot;),g&amp;&amp;a.setRequestHeader(&quot;x-umami-cache&quot;,g),a.onreadystatechange=function(){4===a.readyState&amp;&amp;n(a.response)},a.send(JSON.stringify(e))}(O+&quot;/api/collect&quot;,{type:t,payload:e},(function(t){return g=t}))},M=function(t,e,n){void 0===t&amp;&amp;(t=_),void 0===e&amp;&amp;(e=x),void 0===n&amp;&amp;(n=y),J(&quot;pageview&quot;,R(H(),{website:n,url:t,referrer:e}))},P=function(t,e,n,a){void 0===e&amp;&amp;(e=&quot;custom&quot;),void 0===n&amp;&amp;(n=_),void 0===a&amp;&amp;(a=y),J(&quot;event&quot;,R(H(),{website:a,url:n,event_type:e,event_value:t}))},z=function(t){var e=t.querySelectorAll(q);Array.prototype.forEach.call(e,B)},B=function(t){(t.getAttribute(&quot;class&quot;)||&quot;&quot;).split(&quot; &quot;).forEach((function(e){if(T.test(e)){var n=e.split(&quot;--&quot;),a=n[1],i=n[2],r=L?L:L=function(){&quot;A&quot;===t.tagName?function(t,e){var n=H();n.event_type=e,n.event_value=t;var a=JSON.stringify({type:&quot;event&quot;,payload:n});navigator.sendBeacon(O+&quot;/api/collect&quot;,a)}(i,a):P(i,a)};t.addEventListener(a,r,!0)}}))},C=function(t,e,n){if(n){x=_;var a=n.toString();(_=&quot;http&quot;===a.substring(0,4)?&quot;/&quot;+a.split(&quot;/&quot;).splice(3).join(&quot;/&quot;):a)!==x&amp;&amp;M()}};if(!n.umami){var D=function(t){return P(t)};D.trackView=M,D.trackEvent=P,n.umami=D}if(b&amp;&amp;!A()){v.pushState=t(v,&quot;pushState&quot;,C),v.replaceState=t(v,&quot;replaceState&quot;,C);var I=function(){&quot;complete&quot;===f.readyState&amp;&amp;(M(),k&amp;&amp;(z(f),new MutationObserver((function(t){t.forEach((function(t){var e=t.target;B(e),z(e)}))})).observe(f,{childList:!0,subtree:!0})))};f.addEventListener(&quot;readystatechange&quot;,I,!0),I()}}}(window)}();整理一下：!(function () {
  &quot;use strict&quot;;
  var t = function (t, e, n) {
      var a = t;
      return function () {
        for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
        return n.apply(null, e), a.apply(t, e);
      };
    },
    e = function () {
      var t = window.doNotTrack,
        e = window.navigator,
        n = window.external,
        a = &quot;msTrackingProtectionEnabled&quot;,
        i = t || e.doNotTrack || e.msDoNotTrack || (n &amp;&amp; a in n &amp;&amp; n[a]());
      return &quot;1&quot; == i || &quot;yes&quot; === i;
    };
  !(function (n) {
    var a = n.screen,
      i = a.width,
      r = a.height,
      o = n.navigator.language,
      c = n.location,
      s = c.hostname,
      u = c.pathname,
      l = c.search,
      d = n.localStorage,
      f = n.document,
      v = n.history,
      p = f.querySelector(&quot;script[data-website-id]&quot;);
    if (p) {
      var m,
        g,
        h = p.getAttribute.bind(p),
        y = h(&quot;data-website-id&quot;),
        w = h(&quot;data-host-url&quot;),
        b = &quot;false&quot; !== h(&quot;data-auto-track&quot;),
        S = h(&quot;data-do-not-track&quot;),
        k = &quot;false&quot; !== h(&quot;data-css-events&quot;),
        E = h(&quot;data-domains&quot;) || &quot;&quot;,
        N = E.split(&quot;,&quot;).map(function (t) {
          return t.trim();
        }),
        T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
        q = &quot;[class*=&#039;umami--&#039;]&quot;,
        A = function () {
          return (
            (d &amp;&amp; d.getItem(&quot;umami.disabled&quot;)) ||
            (S &amp;&amp; e()) ||
            (E &amp;&amp; !N.includes(s))
          );
        },
        O = w
          ? (m = w) &amp;&amp; m.length &gt; 1 &amp;&amp; m.endsWith(&quot;/&quot;)
            ? m.slice(0, -1)
            : m
          : p.src.split(&quot;/&quot;).slice(0, -1).join(&quot;/&quot;),
        j = i + &quot;x&quot; + r,
        L = {},
        _ = &quot;&quot; + u + l,
        x = f.referrer,
        H = function () {
          return { website: y, hostname: s, screen: j, language: o, url: _ };
        },
        R = function (t, e) {
          return (
            Object.keys(e).forEach(function (n) {
              t[n] = e[n];
            }),
            t
          );
        },
        J = function (t, e) {
          A() ||
            (function (t, e, n) {
              var a = new XMLHttpRequest();
              a.open(&quot;POST&quot;, t, !0),
                a.setRequestHeader(&quot;Content-Type&quot;, &quot;application/json&quot;),
                g &amp;&amp; a.setRequestHeader(&quot;x-umami-cache&quot;, g),
                (a.onreadystatechange = function () {
                  4 === a.readyState &amp;&amp; n(a.response);
                }),
                a.send(JSON.stringify(e));
            })(O + &quot;/api/collect&quot;, { type: t, payload: e }, function (t) {
              return (g = t);
            });
        },
        M = function (t, e, n) {
          void 0 === t &amp;&amp; (t = _),
            void 0 === e &amp;&amp; (e = x),
            void 0 === n &amp;&amp; (n = y),
            J(&quot;pageview&quot;, R(H(), { website: n, url: t, referrer: e }));
        },
        P = function (t, e, n, a) {
          void 0 === e &amp;&amp; (e = &quot;custom&quot;),
            void 0 === n &amp;&amp; (n = _),
            void 0 === a &amp;&amp; (a = y),
            J(
              &quot;event&quot;,
              R(H(), { website: a, url: n, event_type: e, event_value: t })
            );
        },
        z = function (t) {
          var e = t.querySelectorAll(q);
          Array.prototype.forEach.call(e, B);
        },
        B = function (t) {
          (t.getAttribute(&quot;class&quot;) || &quot;&quot;).split(&quot; &quot;).forEach(function (e) {
            if (T.test(e)) {
              var n = e.split(&quot;--&quot;),
                a = n[1],
                i = n[2],
                r = L
                  ? L
                  : (L = function () {
                      &quot;A&quot; === t.tagName
                        ? (function (t, e) {
                            var n = H();
                            (n.event_type = e), (n.event_value = t);
                            var a = JSON.stringify({
                              type: &quot;event&quot;,
                              payload: n
                            });
                            navigator.sendBeacon(O + &quot;/api/collect&quot;, a);
                          })(i, a)
                        : P(i, a);
                    });
              t.addEventListener(a, r, !0);
            }
          });
        },
        C = function (t, e, n) {
          if (n) {
            x = _;
            var a = n.toString();
            (_ =
              &quot;http&quot; === a.substring(0, 4)
                ? &quot;/&quot; + a.split(&quot;/&quot;).splice(3).join(&quot;/&quot;)
                : a) !== x &amp;&amp; M();
          }
        };
      if (!n.umami) {
        var D = function (t) {
          return P(t);
        };
        (D.trackView = M), (D.trackEvent = P), (n.umami = D);
      }
      if (b &amp;&amp; !A()) {
        (v.pushState = t(v, &quot;pushState&quot;, C)),
          (v.replaceState = t(v, &quot;replaceState&quot;, C));
        var I = function () {
          &quot;complete&quot; === f.readyState &amp;&amp;
            (M(),
            k &amp;&amp;
              (z(f),
              new MutationObserver(function (t) {
                t.forEach(function (t) {
                  var e = t.target;
                  B(e), z(e);
                });
              }).observe(f, { childList: !0, subtree: !0 })));
        };
        f.addEventListener(&quot;readystatechange&quot;, I, !0), I();
      }
    }
  })(window);
})();我们可以看到，每次请求前都是O + &#039;/api&#039;进行的URL拼接，所以我们找到变量O的位置进行修改即可。O = w
          ? (m = w) &amp;&amp; m.length &gt; 1 &amp;&amp; m.endsWith(&quot;/&quot;)
            ? m.slice(0, -1)
            : m
          : p.src.split(&quot;/&quot;).slice(0, -1).join(&quot;/&quot;),我们将这里的O改成我们自己的umami域名，成了这样：!(function () {
  &quot;use strict&quot;;
  var t = function (t, e, n) {
      var a = t;
      return function () {
        for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
        return n.apply(null, e), a.apply(t, e);
      };
    },
    e = function () {
      var t = window.doNotTrack,
        e = window.navigator,
        n = window.external,
        a = &quot;msTrackingProtectionEnabled&quot;,
        i = t || e.doNotTrack || e.msDoNotTrack || (n &amp;&amp; a in n &amp;&amp; n[a]());
      return &quot;1&quot; == i || &quot;yes&quot; === i;
    };
  !(function (n) {
    var a = n.screen,
      i = a.width,
      r = a.height,
      o = n.navigator.language,
      c = n.location,
      s = c.hostname,
      u = c.pathname,
      l = c.search,
      d = n.localStorage,
      f = n.document,
      v = n.history,
      p = f.querySelector(&quot;script[data-website-id]&quot;);
    if (p) {
      var m,
        g,
        h = p.getAttribute.bind(p),
        y = h(&quot;data-website-id&quot;),
        w = h(&quot;data-host-url&quot;),
        b = &quot;false&quot; !== h(&quot;data-auto-track&quot;),
        S = h(&quot;data-do-not-track&quot;),
        k = &quot;false&quot; !== h(&quot;data-css-events&quot;),
        E = h(&quot;data-domains&quot;) || &quot;&quot;,
        N = E.split(&quot;,&quot;).map(function (t) {
          return t.trim();
        }),
        T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
        q = &quot;[class*=&#039;umami--&#039;]&quot;,
        A = function () {
          return (
            (d &amp;&amp; d.getItem(&quot;umami.disabled&quot;)) ||
            (S &amp;&amp; e()) ||
            (E &amp;&amp; !N.includes(s))
          );
        },
        O = &#039;https://www.dwt.life&#039;,
        j = i + &quot;x&quot; + r,
        L = {},
        _ = &quot;&quot; + u + l,
        x = f.referrer,
        H = function () {
          return { website: y, hostname: s, screen: j, language: o, url: _ };
        },
        R = function (t, e) {
          return (
            Object.keys(e).forEach(function (n) {
              t[n] = e[n];
            }),
            t
          );
        },
        J = function (t, e) {
          A() ||
            (function (t, e, n) {
              var a = new XMLHttpRequest();
              a.open(&quot;POST&quot;, t, !0),
                a.setRequestHeader(&quot;Content-Type&quot;, &quot;application/json&quot;),
                g &amp;&amp; a.setRequestHeader(&quot;x-umami-cache&quot;, g),
                (a.onreadystatechange = function () {
                  4 === a.readyState &amp;&amp; n(a.response);
                }),
                a.send(JSON.stringify(e));
            })(O + &quot;/api/collect&quot;, { type: t, payload: e }, function (t) {
              return (g = t);
            });
        },
        M = function (t, e, n) {
          void 0 === t &amp;&amp; (t = _),
            void 0 === e &amp;&amp; (e = x),
            void 0 === n &amp;&amp; (n = y),
            J(&quot;pageview&quot;, R(H(), { website: n, url: t, referrer: e }));
        },
        P = function (t, e, n, a) {
          void 0 === e &amp;&amp; (e = &quot;custom&quot;),
            void 0 === n &amp;&amp; (n = _),
            void 0 === a &amp;&amp; (a = y),
            J(
              &quot;event&quot;,
              R(H(), { website: a, url: n, event_type: e, event_value: t })
            );
        },
        z = function (t) {
          var e = t.querySelectorAll(q);
          Array.prototype.forEach.call(e, B);
        },
        B = function (t) {
          (t.getAttribute(&quot;class&quot;) || &quot;&quot;).split(&quot; &quot;).forEach(function (e) {
            if (T.test(e)) {
              var n = e.split(&quot;--&quot;),
                a = n[1],
                i = n[2],
                r = L
                  ? L
                  : (L = function () {
                      &quot;A&quot; === t.tagName
                        ? (function (t, e) {
                            var n = H();
                            (n.event_type = e), (n.event_value = t);
                            var a = JSON.stringify({
                              type: &quot;event&quot;,
                              payload: n
                            });
                            navigator.sendBeacon(O + &quot;/api/collect&quot;, a);
                          })(i, a)
                        : P(i, a);
                    });
              t.addEventListener(a, r, !0);
            }
          });
        },
        C = function (t, e, n) {
          if (n) {
            x = _;
            var a = n.toString();
            (_ =
              &quot;http&quot; === a.substring(0, 4)
                ? &quot;/&quot; + a.split(&quot;/&quot;).splice(3).join(&quot;/&quot;)
                : a) !== x &amp;&amp; M();
          }
        };
      if (!n.umami) {
        var D = function (t) {
          return P(t);
        };
        (D.trackView = M), (D.trackEvent = P), (n.umami = D);
      }
      if (b &amp;&amp; !A()) {
        (v.pushState = t(v, &quot;pushState&quot;, C)),
          (v.replaceState = t(v, &quot;replaceState&quot;, C));
        var I = function () {
          &quot;complete&quot; === f.readyState &amp;&amp;
            (M(),
            k &amp;&amp;
              (z(f),
              new MutationObserver(function (t) {
                t.forEach(function (t) {
                  var e = t.target;
                  B(e), z(e);
                });
              }).observe(f, { childList: !0, subtree: !0 })));
        };
        f.addEventListener(&quot;readystatechange&quot;, I, !0), I();
      }
    }
  })(window);
})();最后使用在线工具混淆一下：https://www.sojson.com/https://tool.lu/js我们需要调试，所以选择tool.lu，新建一个u.js，保存加密的js再创建一个页面引用js：最后效果：因为我用的博客域名，实际换成umami即可。</description>
</item>
<item rdf:about="https://dwt.life/archives/228/">
<title>UMAMI隐私统计安装及配置</title>
<link>https://dwt.life/archives/228/</link>
<dc:date>2022-04-27T12:20:00+08:00</dc:date>
<description>UMAMI使用nodejs编写，是一个用于替代Google Analytics等第三方统计平台，更专注于用户隐私保护的自建统计项目。环境这里使用两种方法去安装umami，第一种是使用pm2或者supervisor直接运行在本机环境的，第二种是使用docker pull并build镜像。如果已经安装了宝塔，推荐直接运行在本机环境中。需要准备的有：MySQLnodejsnpmpm2（或者supervisor）Ubuntu系列安装Nodejs、npm：curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
apt-get install -y nodejs
apt-get install -y npmCentos系列可以直接使用yum：yum install node npm -yMySQL建议查看其他博客的安装及配置。如果您选择使用docker安装，那么仅需这样的一步：curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun也可以使用国内daocloud的脚本：curl -sSL https://get.daocloud.io/docker | sh安装及配置如果您需要使用docker进行安装，请直接转到docker方式章节。安装git clone https://github.com/mikecao/umami.git
cd umami
npm install导入数据库mysql -u username -p databasename &lt; sql/schema.mysql.sql我这里就直接在PMA中执行了配置.envDATABASE_URL=mysql://username:mypassword@localhost:3306/databasenameHASH_SALT=随机生成optimize&build在umami目录下执行：npm run build即可生成编译后的工程文件启动程序按照官方readme说的直接npm start是可以的，默认会运行在3000，但是我们如果关闭了终端会话，也会停止运行，所以需要使用PM2或者supervisor进行后台保活运行，当然，也可以使用nohup。如果我们使用的是第一种本机环境运行，那么可以通过npm安装pm2：npm install pm2 -g在umami目录下执行：pm2 start npm --name umami -- start
pm2 startup
pm2 save由于服务器上已经有宝塔和pm2管理器了，所以我并不打算使用命令行添加。docker方式安装注意，如果您选择docker方式，那么您可以完成后直接跳过安装及配置这一章首先您需要docker环境，上述步骤已经提供了脚本。然后执行docker-compose up确保服务已经启动。我们这里使用MySQL作为数据库后端：docker pull ghcr.io/mikecao/umami:mysql-latest稍微等待数十分钟即可启动，默认占用端口3000。升级docker-compose pull
docker-compose up --force-recreate域名反代在nginx.conf中的http段下添加：server{
    server_name 你的域名;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}或者宝塔，网站管理，反向代理即可。文件修改及更新假如你按照前面的pm2运行，那么按照如下步骤执行pm2 stop umami停止其运行。切换到你的umami目录pull一份最新的代码git pull重新安装node modules npm install重新编译npm run build重新启动PM2pm2 restart umami根据参考博文中提到的更新失败可以执行git reset --hard使用首次登录默认账号admin，密码umami添加统计网站获取统计代码查看统计由于我并没有打算立刻使用umami，使用官方的图做一个收尾吧：参考https://github.com/mikecao/umamihttps://www.himiku.com/archives/umami.html</description>
</item>
</rdf:RDF>