2025-02-27 · 日常

小窝装修记

前言

小窝装修其实早在 23 年 8 月就已经开始有想法了,到了 23 年 9 月底开始动工翻新了。

最开始装修时想的是依旧使用 Typecho 作为 CMS,通过 Typecho 的 Restful API 插件提供 API 接口进行前后端交互,而前端则使用 Nuxt 3 以及 Element UI 前端 UI 框架来实现,并且总体布局还是与先前主题 Cuckoo 一致保持双栏样式,但总体风格偏向于简单、纯白。但在 23 年底因为期末哇还有一些别的事情哇导致一度停工,到了今年一看发现装修部件 Nuxt 3 的版本都落后很多了(悲)。

于是到了今年 2 月花了些时间又完全重新规划和装修,但这次与初版装修有所不同,这次使用的是 Nuxt Content v3 作为 CMS,前端框架选用 Shadcn-vue,并且改成了个人主页和文章整合在一起,虽然装修后还有很多地方有些欠缺,比如暗色模式、标签、灯箱、表情包啥的,就像是装修后还缺少点家具似的 hhh,待后续慢慢丰富起来吧。

(起初想着原来在 Typecho 的评论很多难迁移,但是看到 Artalk 支持 Typecho 数据迁移所以就没啥顾虑了,直接开工力!)

Nuxt Content

选用 Nuxt Content 作为 CMS 也是因为看到了许多用它来重构 Blog 的文章,满足我所想要的功能,就是在折腾的时候有点难顶。因为最开始看到重构的文章都用的 v2 版本,而我用的是新出的 v3 版本。

新版本中,与旧版相比区别挺大。

  • 使用 queryCollection() 代替了 queryContent()
  • <ContentDoc><ContentList><ContentNavigation> 和 <ContentQuery> 均被移除。

在 v3 版本中,当需要获取文章数据时只需要使用:

const route = useRoute()
const { data: page } = await useAsyncData(route.path, () => {
  return queryCollection('post').path(route.path).first()
})

想要根据时间排序且筛选进行部分输出可以使用 order()

const { data: page } = await useAsyncData(() => {
  return queryCollection('post')
    .select('path', 'title', 'description', 'date', 'category')
    .order('date', 'DESC')
    .all()
})

想要实现分页功能可以使用 skip()limit()

const { data: page } = await useAsyncData(() => {
  return queryCollection('post')
    .select('path', 'title', 'description', 'date', 'category')
    .order('date', 'DESC')
    .skip(5)
    .limit(5)
    .all()
})

对于想修改默认 HTML 排版的可以在 components/content/ 创建对应标签组件进行覆盖,详细可参考 官网文档 。若想实现特殊样式,比如警示框之类的,则同样可以在 components/content/ 创建自定义的组件,以 components/content/Callout.vue 为例,编写 Markdown 则可以通过这种方式来调用:

::callout
This is a callout.
::

当然,你可以通过这种方式开实现表情包等功能(后续看看有什么好用的方案装修上去)。

文章源建议可以自行多开一个 Github 私有库,从整体中分离出来,这样可以避免 Github Action 进行多余的部署操作(当然,也可以通过 if 来避免多余部署操作)。其中,CONTENT_REPO_TOKEN 是申请的 Github 密钥。

export default defineContentConfig({
  collections: {
    post: defineCollection({
      type: 'page',
      source: {
        include: 'post/*.md',
        repository: 'https://github.com/bhaoo/xxxxxxx',
        authToken: process.env.CONTENT_REPO_TOKEN
      },
      schema: z.object({
        date: z.string(),
        category: z.string(),
      })
    }),
  }
})

Sitemap

@nuxtjs/sitemap 对于 Nuxt Content v3 做了兼容,通过以下方法即可实现将文章内容加入站点地图中:

import { defineContentConfig, defineCollection, z } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'

export default defineContentConfig({
  collections: {
    post: defineCollection(
      asSitemapCollection({
        type: 'page',
        source: {
          include: 'post/*.md',
          repository: 'https://github.com/bhaoo/xxxxxxx',
          authToken: process.env.CONTENT_REPO_TOKEN
        },
        schema: z.object({
          date: z.string(),
          category: z.string(),
        })
      }),
    ),
  }
})

Umami

(于 2025-04-03 更新)

之前还在用着百度统计但随着去年还是前年改版后就基本换到 Umami 了,在用 Cuckoo 的时候,是不通过代理的形式直接请求 Umami 的 API 接口的,此时的请求是这样的:

client → Cloudflare(Umami) → Umami

这次换到 Nuxt 后则用的是 Nuxt Umami 来实现,并且使用了简单的代理来实现。起初,发现能正常使用就没管,但这段时间发现地理位置信息有误,到了 Nginx 日志一看,请求的 IP 地址都是服务器 IP 地址,跑到 Umami 的仓库看到了这些:

export const IP_ADDRESS_HEADERS = [
  'cf-connecting-ip',
  'x-client-ip',
  'x-forwarded-for',
  'do-connecting-ip',
  'fastly-client-ip',
  'true-client-ip',
  'x-real-ip',
  'x-cluster-client-ip',
  'x-forwarded',
  'forwarded',
  'x-appengine-user-ip',
];
export function getIpAddress(headers: Headers) {
  const customHeader = process.env.CLIENT_IP_HEADER;

  if (customHeader && headers.get(customHeader)) {
    return headers.get(customHeader);
  }

  const header = IP_ADDRESS_HEADERS.find(name => {
    return headers.get(name);
  });

  const ip = headers.get(header);

  if (header === 'x-forwarded-for') {
    return ip?.split(',')?.[0]?.trim();
  }

  if (header === 'forwarded') {
    const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);

    if (match) {
      return match[1];
    }
  }

  return ip;
}

由于 cf-connecting-ip 放在了 IP_ADDRESS_HEADERS 首位,并且因为开启简单代理后的请求路径如下:

client → Cloudflare(Blog) → Nitro → Cloudflare(Umami) → Umami

当请求客户端到 Nitro 时,cf-connecting-ip 还是准确的客户端 IP 地址,但因简单代理的套娃后,此时的 cf-connecting-ip 则变成了 Nitro 所在服务器的 IP 地址。

因此解决的办法有两种:

第一种则是将 Nuxt Umami 的代理模式设置为 false ,即跟之前一样,直连至 Umami ,配置如下:

export default defineNuxtConfig({
  umami: {
    ...others,
    proxy: 'false',
  },
});

第二种则是将 Nuxt Umami 的代理模式设置为 direct ,然后通过内网方式请求至 Umami ,配置如下:

(以 Blog 和 Umami 均在同个 Docker 网络中为例)

export default defineNuxtConfig({
  umami: {
    ...others,
    host: 'http://umami-1:3000/',
    proxy: 'direct',
  },
});

此时请求路径则变成:

client → Cloudflare(Blog) → Nitro → Umami

Artalk

(于 2025-04-03 更新)

Artalk 同理,如果使用的是 nitro 代理的话需要前往服务端设置 - 服务器 - 代理标头名,填写 X-Forwarded-For 以来获取用户真实 IP,Nuxt 的配置如下:

export default defineNuxtConfig({
  nitro: {
    devProxy: {
      "/artalk": {
        target: "http://artalk:3000",
        changeOrigin: true,
      },
    },
    routeRules: {
      "/artalk/**": {
        proxy: "http://artalk:3000/**",
      },
    },
  },
});

部署

部署这一块已经可以看看我之前的这篇文章哦 -> Github Actions 实现 Nuxt 3 自动构建并部署

后言

总的来说装修还没完工,还在逐渐更新中。

Cuckoo 的话也不会就此停更,如果是想要看 Cuckoo Demo 的话可以点这里哦。

本文采用 CC BY-NC-SA 4.0 许可协议,允许您分享和改编,但仅限于非商业用途。转载时,请务必附上原文出处链接及上述许可声明。

...
© 2018-2026 Bhao.|萌ICP备202023323
Designed by Bhao|Made with love | Cuckoo Demo