惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
Scott Helme
Scott Helme
S
Securelist
美团技术团队
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
SegmentFault 最新的问题
W
WeLiveSecurity
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Apple Machine Learning Research
Apple Machine Learning Research
The Cloudflare Blog
AI
AI
L
Lohrmann on Cybersecurity
S
Security Affairs
Cloudbric
Cloudbric
SecWiki News
SecWiki News
爱范儿
爱范儿
雷峰网
雷峰网
Engineering at Meta
Engineering at Meta
C
Cyber Attacks, Cyber Crime and Cyber Security
大猫的无限游戏
大猫的无限游戏
N
News and Events Feed by Topic
I
InfoQ
S
Secure Thoughts
AWS News Blog
AWS News Blog
A
About on SuperTechFans
Schneier on Security
Schneier on Security
酷 壳 – CoolShell
酷 壳 – CoolShell
The Last Watchdog
The Last Watchdog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Check Point Blog
P
Palo Alto Networks Blog
博客园 - 【当耐特】
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News
Latest news
Latest news
I
Intezer
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
D
Docker

BMPI

构建自己的信息简报 2.0 我的退休计划:把无期变成有期 从情绪化交易到系统化投资 策引2025 实盘大考:当算法跑赢人性 三十五 我的AI投资助手 我的投资之路 BMPI周记008:设止损而勇试错 BMPI周记007:经济下行 BMPI周记006:时光机 BMPI周记005:直播小完结 BMPI周记004:不要做空 BMPI周记003:十年前的今天 BMPI周记002:交易之难 BMPI周记001:开始直播 对交易的思考 AI驱动开发:从Prompt到Product(直播) 策引全球投资组合:A股1号 策引全球投资组合:A股2号 策引全球投资组合:A股3号 策引全球投资组合:A股全球 策引全球投资组合:加密币1号 策引全球投资组合:加密币2号 策引全球投资组合:美股1号 策引全球投资组合:美股2号 策引全球投资组合:美股3号 策引全球投资组合:美股4号 策引全球投资组合:美股全球 全球投资开户完全指南:A股与美股篇 我的2023 全球经济中的隐形巨人 海外银行证券开户薅羊毛小记 我与ChatGPT结对编程的体验 ChatGPT背后的语言模型简史 ChatGPT应用开发小记 我的AI阅读助手 SQLite的文艺复兴 2022亏了多少 # 组合季报(2022Q4) AI降临 构建自己的信息简报 善用GitHub Real-time Web应用开发新体验 我的巨亏经历 # 组合季报(2022Q3) Google软件工程之工具篇 构建自己的杠杆 我的投资助手 Google软件工程之过程篇 Google软件工程之文化篇 交易之难 # 组合季报(2022Q2) 从技术难题中学习 编程语言是如何实现并发的之并发模型篇 疫情与战争 # 组合季报(2022Q1) 编程语言是如何实现并发的之操作系统篇 编程语言是如何实现泛型的 写在第二十五万字 走进 Web3 财富常识 复盘2021 # 组合季报(2021Q4) 分布式系统中的时间 分布式系统下的认证与授权 如何学习一门技术 K8S 云原生应用开发小记 使用 Beancount 管理家庭财务 三周年小记 # 组合月报(202109) 仓位管理是核心 # 组合月报(202108) 云端 IDE 护城河还在但城没了 # 组合月报(202107) 构建高质量的信息输入渠道 最牛指数 # 组合月报(202106) 国际化与本地化 慢慢变富 # 组合月报(202105) 失败驱动开发 加密币挖矿小记 家庭资产配置的阶段 # 组合月报(202104) OKR + GTD + Note => Logseq 我的个人项目技术栈 长期投资之难 # 组合月报(202103) 我的人生管理系统 当别人的股票基金上涨时 # 组合月报(202101) 构建终身学习体系进行自我提升 我的家庭理财规划 投资的秘密 # 组合月报(202012) 零成本搭建现代博客之优化国内访问速度 我的绘图工具箱 关于银行分期贷款的坑 # 组合月报(202011) 投资交易的心理建设 # 组合月报(202010) 大跌时我们能做什么 # 组合月报(202009) 双均线交易策略 # 组合月报(202008) Serverless应用开发小记 投资理财书籍推荐 # 组合月报(202007) 200万小目标 # 组合月报(202006) 零成本搭建现代博客之加载速度优化篇 零成本搭建现代博客之订阅推送篇 我常用的浏览器插件 零成本搭建现代博客之SEO优化篇 什么是SEO 零成本搭建现代博客之搭建篇 用Markdown可以做什么 什么是互联网广告 使用AWS Lambda提高网站图片加载速度1X倍
基于Serverless实现静态博客访问统计功能
马大伟 · 2021-01-24 · via BMPI

6:18 | 1386 字 | 次阅读

背景

我在 零成本搭建现代博客指南 系列文章里详细介绍了本博客的搭建过程,本站是基于 Hugo 框架生成的,并部署在 Vercel 平台上。

和最流行的 CMS 框架 Wordpress 不同,Hugo/Hexo/Gatsby 等静态站点生成的技术好处在于:

  • 成本极低甚至免费,无需专门的 VPS 服务器,只需要将生成好的静态页面托管到 CDN/GitHub Pages/Vercel/Netlify/AWS S3 这类平台上即可;
  • Wordpress 并发性能差,而静态博客只受限于与流量带宽的限制,不存在性能和并发问题。

但和 Wordpress 相比,因为全站都是静态化生成的页面,没有后台服务器的接口支持,无法实现一些很简单的带状态的功能,如不支持页面阅读数量统计功能。

这种 Jamstack 架构的站点要实现动态的功能,一般会采用 Serverless 的方式去实现。

为何要实现博客的访问统计功能

本博客早已接入了 Google Analytics ,但是为何还要实现博客的访问统计功能?一方面考虑到文章的阅读数量可以供读者更容易识别此篇文章是否是热门文章,另一方面这种阅读量数据对百度 SEO 有正向提升作用。Google Analytics 的问题在于一旦用户安装了某些屏蔽广告插件后,同时也会导致 GA 无法正常工作,根据某些文章统计说一般会损失近 10% 的流量统计。

实现访问统计功能的技术选择

在选择实现此功能的过程中,我有如下选择:

因为之前使用过 Terraform 实现基于 AWS 平台的 Serverless 架构,具体过程可以参加这篇 Serverless应用开发小记 ,所以我最终选择了第三种方式去做。

这次我使用 Pulumi 实现 Serverless 架构。Pulumi 相比 Terraform 的优势在于可以以编程方式实现 Infrastructure as code ,相信看完该篇文章你就能知道它的强大了。

使用 Pulumi 实现博客访问统计接口

先创建一个 Pulumi 工程:

我们选 AWS + TypeScript 。

之后按提示创建目录及安装依赖,工程目录如下:

.
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── README.md
├── index.ts
├── package-lock.json
├── package.json
└── tsconfig.json

只需要编辑 index.ts 即可,代码如下:

import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as pulumi from "@pulumi/pulumi";

// Create a mapping from 'route' to a count
const counterTable = new aws.dynamodb.Table("api-bmpi-dev", {
    attributes: [{
        name: "id",
        type: "S",
    }],
    hashKey: "id",
    readCapacity: 5,
    writeCapacity: 5,
});

// let bmpiAllPageViewsRoute = "bmpi-dev-all-page-views"; // all page views key

// Create an views API endpoint
const viewsEndpoint = new awsx.apigateway.API("bmpi-dev-post-views", {
    routes: [{
        path: "/{route+}",
        method: "GET",
        eventHandler: async (event) => {
            const route = event.pathParameters!["route"];

            console.log(`Getting count for '${route}'`);

            const client = new aws.sdk.DynamoDB.DocumentClient();

            const result = await client.update({
                TableName: counterTable.name.get(),
                Key: { id: route },
                UpdateExpression: "SET hit = if_not_exists(hit, :zero) + :incr",
                ExpressionAttributeValues: { ":zero": 0, ":incr": 1 },
                ReturnValues:"UPDATED_NEW",
            }).promise();

            let count = result.Attributes!.hit;
            
            return {
                statusCode: 200,
                headers: {
                    "Access-Control-Allow-Headers" : "Content-Type",
                    "Access-Control-Allow-Origin": "https://www.bmpi.dev",
                    "Access-Control-Allow-Methods": "OPTIONS,POST,GET"
                },
                body: JSON.stringify({ route, count }),
            };
        },
    }],
});

// We have registered a domain, and a cert already (we could do most of this with Pulumi as well if we wanted!)
let domainName = "api.bmpi.dev"; // `bmpi.dev` is regitered with Route53 
let route53DomainZoneId = "Z3D746J1ZFXBYQ"; // The Hosted Zone I got when I registered `bmpi.dev`
let certARN = "arn:aws:acm:us-east-1:745121664662:certificate/6e610430-46c8-4884-a1f7-471be45decf4"; // ACM cert for `*.bmpi.dev`

// API Gateway requires we register the Domain with it first
const domain = new aws.apigateway.DomainName("domain", {
    certificateArn: certARN,
    domainName: domainName,
});

// Then we can map a REST API to a domain with a BasePathMapping
const mapping = new aws.apigateway.BasePathMapping("mapping", {
    restApi: viewsEndpoint.restAPI,
    basePath: "page-views", // We map our API into the "/page-views" base path
    stageName: viewsEndpoint.stage.stageName, // We map the stage we got for free with `.x.API` above
    domainName: domain.domainName, // We map it into the domain we registered above
});

// Finally, we need a DNS reocrd to point at our API Gateway
const record = new aws.route53.Record("record", {
    type: "A",
    zoneId: route53DomainZoneId,
    name: domainName, // Write a record for `api.bmpi.dev` into the zone for `bmpi.dev`
    aliases: [{
        name: domain.cloudfrontDomainName, // APIGateway provides it's own CloudFront distribution we can point at...
        zoneId: domain.cloudfrontZoneId,
        evaluateTargetHealth: true,
    }],
});

// It might take a while after we deploy the record above before the DNS propagates and allows us to resolve these URLs...
export let endpoint = pulumi.interpolate`https://${record.name}/page-views`;

所有的代码就这些,然后一键部署上线:

不到 100 行的代码我们完成了什么功能?

  1. 创建一个 dynamodb 表;
  2. 实现了一个 Serverless API,支持以原子性更新特定 URL 的访问计数,并且开启了 CORS 支持;
  3. 创建了一个 API Gateway 并且绑定了 api.bmpi.dev 域名且开启了 HTTPS 支持;
  4. 创建了 api.bmpi.dev 域名的 DNS 记录;
  5. 输出了此接口的请求地址。

体会到 Pulumi 的强大了吗?一行代码完成了很多基础设施的配置,这就是 IaC 的强大!

之后的工作就简单了,我们只需要在页面通过 JS 调用此接口即可,代码见这个 commit: add post page hit count feature

最终效果见:

总结

Jamstack + Serverless 是实现现代 Web 开发很流行的方式,尤其是在做一些个人项目的时候,一个人就是一个团队。