Featured image of post 通过Umami实现访客数、浏览量

通过Umami实现访客数、浏览量

获取 Token

Umami 是有 API 的 (API Reference)
但是这玩意需要账号密码才能访问,不过账号密码也是用来拿 Token 的,所以我们直接拿 Token 就行了
这个 Token 无论如何都要在前端使用的,所以直接拿得了🫠
回头看看写个后端把这玩意封装一下

打开 Umami 后台,打开一个网页概览,随便找一个 /api/ 结尾的请求

API 访问请求

在请求标头里找到 AuthorizationCookie copy下来之后替换掉后面代码中的 :authorization:cookie

浏览量、访客、访问次数

文档:GET /api/websites/:websiteId/stats
打开 params.toml,在 [footer]customText 中添加以下内容

1
<span id="visit-info"></span>

layouts\partials\footer\custom.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 浏览量、访客、访问次数 -->
<script>
    (async () => {
        const spanElement = document.getElementById('visit-info')
        let pageviews = 0
        let visitors = 0
        let visits = 0
        const endOfDay = new Date()
        endOfDay.setHours(23, 59, 59, 999)
        const endAt = endOfDay.getTime()
        const headers = {
            'authorization': ':authorization',
            'cookie': ':cookie'
        }
        try {
            const res = await fetch(`https://umami.trrw.tech/api/websites/:websiteId/stats?startAt=:startAt&endAt=${endAt}&unit=day&timezone=Asia%2FShanghai`, { headers: headers })
            const viewsData = await res.json()
            pageviews = viewsData.pageviews || 0
            visitors = viewsData.visitors || 0
            visits = viewsData.visits || 0
        } finally {
            spanElement.innerText = `浏览量${pageviews} · 访客${visitors} · 访问次数${visits}`
        }
    })()
</script>

获取 :websiteId

:websiteId

:startAt 可以在后台把时间调成 所有时间段 后找到这个stats请求来获得

:startAt

最终效果
页脚

文章浏览量

文档:GET /api/websites/:websiteId/metrics/expanded

layouts\partials\article\components\details.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<div class="article-details">
    {{ if .Params.categories }}
    <header class="article-category">
        {{ range (.GetTerms "categories") }}
            <a href="{{ .RelPermalink }}" {{ with .Params.style }}style="background-color: {{ .background }}; color: {{ .color }};"{{ end }}>
                {{ .LinkTitle }}
            </a>
        {{ end }}
    </header>
    {{ end }}

    <div class="article-title-wrapper">
        <h2 class="article-title">
            <a href="{{ .RelPermalink }}">
                {{- .Title -}}
            </a>
        </h2>
    
        {{ with .Params.description }}
        <h3 class="article-subtitle">
            {{ . }}
        </h3>
        {{ end }}
    </div>

    {{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }}
    {{ $showDate := not .Date.IsZero }}
    {{ $showFooter := or $showDate $showReadingTime }}
    {{ if $showFooter }}
    <footer class="article-time">
        {{ if $showDate }}
            <div>
                {{ partial "helper/icon" "date" }}
                <time class="article-time--published" datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
                    {{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
                </time>
            </div>
        {{ end }}

        {{ if $showReadingTime }}
            <div>
                {{ partial "helper/icon" "clock" }}
                <time class="article-time--reading">
                    {{ T "article.readingTime" .ReadingTime }}
                </time>
            </div>
        {{ end }}

        <!-- 这里是新增的,其余都是主题原本的功能 -->
        <!-- 浏览量 -->
        <div>
            {{ partial "helper/icon" "read" }}
            <time class="article-analysic" data-path="{{ .RelPermalink }}">
                0 阅读
            </time>
        </div>
        <!-- ⬆️⬆️⬆️ -->
    </footer>
    {{ end }}

    {{ if .IsTranslated }}
        <footer class="article-translations">
            {{ partial "helper/icon" "language" }}
            <div>
                {{ range .Translations }}
                    <a href="{{ .Permalink }}" class="link">{{ .Language.LanguageName }}</a>
                {{ end }}
            </div>
        </footer>
    {{ end }}
</div>

这里 {{ partial "helper/icon" "read" }} 是对应的 assets/icons 下的svg,这里就是 assets/icons/read.svg
这里贴一下svg代码,省的找了

1
2
3
4
5
6
7
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
    stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
    class="icon icon-tabler icons-tabler-outline icon-tabler-eye">
    <path stroke="none" d="M0 0h24v24H0z" fill="none" />
    <path d="M2 12s3 -7 10 -7s10 7 10 7s-3 7 -10 7s-10 -7 -10 -7" />
    <circle cx="12" cy="12" r="2" />
</svg>

layouts\partials\footer\custom.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 文章浏览量 -->
<script>
    (async () => {
        const els = document.querySelectorAll('.article-analysic')
        if (!els.length) return

        const endOfDay = new Date()
        endOfDay.setHours(23, 59, 59, 999)
        const endAt = endOfDay.getTime()
        const headers = {
            'authorization': ':authorization',
            'cookie': ':cookie'
        }
        for (const el of els) {
            const search = el.dataset.path || '/'
            let pageviews = 0
            try {
                const res = await fetch(`https://umami.trrw.tech/api/websites/:websiteId/metrics/expanded?startAt=:startAt&endAt=${endAt}&unit=day&timezone=Asia%2FShanghai&search=${search}&type=path`, { headers: headers })
                const data = await res.json()
                if (Array.isArray(data) && data.length > 0)
                    pageviews = data[0].pageviews || 0
            } finally {
                el.textContent = `${pageviews} 阅读`
            }
        }
    })()
</script>

最终效果
最终效果

附录

参考文献

  1. Umami API Reference

文章封面

文章封面