标签归档:开发经验

如何在 Google Analytics 4(GA4) 查看 Referer URL ,获取来源地址

作为统计站的第一,这个 Blog 也挂了 GA 4 作为统计。如果你希望知道是谁在推荐你的 Blog,一个很好的办法是查看 HTTP 的 Referer 的 URL,来判断哪些人在哪些地方推荐了你。

不过 Google Analytics 在升级到 GA4 之后,查看 Referer  变得麻烦了不少,没办法直接通过预置的看板来查看。这篇文章就是帮你找回丢失 Referer URL。

具体操作步骤

一、登录 GA 4 ,找到你的站点;点击左侧的「探索」,进入到探索页面。在探索页面点击「空白」,来创建一个新的探索看板

    image

    二、新的探索页面,选择维度这里,新增两个维度和一个指标

    维度:网页引荐来源网址网页位置

    指标:新用户数

    image

    三、将网页位置和网页引荐来源网址配置到设置中的行,且顺序为网页位置在先,网页引荐来源网址在后;显示行数设置为 500;新用户数配置到设置中的值当中;设置过滤器为网页引荐来源网址包含 //

    image

    四、配置完成后,你就可以看到类似我这样的界面了,在这个页面里,你就可以看到不同的来源给你带来了多少流量;从而进一步的去和对方沟通~

    image

    django-storages 配置使用 S3 Provider 支持 Aliyun OSS 使用

    阿里云 OSS 提供了 S3 的兼容,所以如果你在 Django 应用当中,希望使用 OSS 作为文件存储的话,可以参考下方的说明,来使用。

    安装

    首先,你需要执行如下命令安装 django-storages 的 S3 兼容

    # uv
    uv add django-storages[s3]
    # pip
    pip install django-storages[s3]
    Code language: CSS (css)

    配置

    接下来,就是在你的项目文件夹中的 settings.py 中添加如下配置

    AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID") # 你在阿里云拿到的 ACCESS_KEY
    AWS_S3_SECRET_ACCESS_KEY = env("AWS_S3_SECRET_ACCESS_KEY") # 你在阿里云拿到的 Secret Key
    AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")# 你在阿里云拿到的 Bucket Name
    AWS_LOCATION = env("AWS_LOCATION") # 你的文件上传路径,比如  uploads/,你的所有文件都会上传到这个路径下
    AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME") # 你的 OSS 的可用区,比如 oss-cn-beijing
    AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN") # 你自己的自定义域名,以便于后续访问的时候使用。如果不知道的话,可以填 bucket 的默认域名。
    AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL") #  你的阿里云 Endpoint URL,比如 https://oss-cn-beijing.aliyuncs.com
    
    AWS_S3_ADDRESSING_STYLE = "virtual" # 阿里云只支持二级域名的形式
    AWS_S3_SIGNATURE_VERSION = "s3" # 阿里云只支持 v2 版的签名逻辑
    
    # 配置默认使用 S3 Storage,即使用 OSS 的 URL
    STORAGES = {
        "default": {
            "BACKEND": "storages.backends.s3.S3Storage", # 使用 S3  Storage
        },
    }
    
    Code language: PHP (php)

    参考上方的配置,添加配置项后,保存,并重启服务器,即可在代码中进行测试。

    测试代码

    你可以执行 python manage.py shell 并执行如下代码,如果无报错,且可以在 OSS 控制台看到文件,则说明你的配置成功了。

    
    from django.core.files.storage import default_storage
    from django.core.files.base import ContentFile
    content = ContentFile(b"Hello World!")
    path = default_storage.save('test_file2.txt', content)
    print(f"文件保存路径: {path}")
    # 测试文件读取
    if default_storage.exists(path):
        with default_storage.open(path, 'r') as f:
            content = f.read()
            print(f"文件内容: {content}")
    # 测试文件URL生成
    url = default_storage.url(path)
    print(f"文件URL: {url}")
    # 测试文件删除
    default_storage.delete(path)
    print(f"文件是否存在: {default_storage.exists(path)}")
    
    Code language: PHP (php)

    一些我自己用的还不错的 Chrome 插件

    英语/日语翻译:沉浸式翻译

    我的日常要看到很多英文文章和网站,因此,可以借助沉浸式翻译,帮助我快速翻译多种语言为中文,从而降低我在阅读不同语言内容的障碍。

    1743588697 image

    快速搜索:超级搜索

    超级搜索支持我快速的选中词汇并进行搜索,对于一些特定的场景下,会非常有帮助。比如我在浏览网页的时候,发现了一部电影,想快速在豆瓣电影中找到并标记,就可以借助超级搜索,配置一个搜索关键词来实现。

    1743588684 image

    SEO 检查器:AITDK

    AI TDK 是我用来检查自己的网页是否完成了一些基本的 SEO 设置的工具。当我上线了一个新的网站后,就会打开 AITDK,然后查看哪里的信息还不完整,需要补充的。就可以继续去补充相应的内容。

    1743588868 image

    JSON 查看:JSON Viewer

    在开发的时候,经常会有要查看服务端返回的 JSON 的情况, 借助 JSON Viewer 可以将不容易看明白的 JSON 给格式化了,方便你快速定位要看的 JSON。

    1743589100 image

    广告拦截:AdGuard

    广告拦截我选择了 AdGuard,有了它,我看 Youtube 再也没有广告了。。。

    1743589301 image

    复制为 Markdown:Copy as Markdown

    因为经常要将部分内容复制为 Markdown,方便在我的其他工具中使用,所以我安装了这个 Copy as Markdown 插件,方便自己随意复制。

    1743589434 image

    页面增强:篡改猴

    当我需要对一些网页做一些快速的改造,但同时又不想写成 Chrome 插件的时候,就会选择写成油猴脚本,然后放在篡改猴里来用,非常方便。

    1743589508 image

    灵感记录:Memos

    我自己部署了一个 Memos ,用于记录自己的灵感和想法。因此,我使用了一个 Chrome 插件,来方便我记录。

    1743589800 image

    记一次发现小宇宙 iOS 版的跳转注入漏洞

    漏洞风险:可以在其他应用借助小宇宙端内跳转任何网页。

    此文章发布前,小宇宙已经修复了这个问题,所以你们可能不能复现这个问题了。

    先看漏洞效果,这个漏洞的问题是你可以在小宇宙里跳转到任何网站,甚至是 PornHub。不过这个 Bug 不重要,重要的是发现 Bug 的过程

    漏洞效果

    0. 背景

    在一天晚上,我在和朋友聊小宇宙的 URL Scheme,想要做个功能,可以实现一键打开小宇宙的节目页面。但卡在我面前的是,我不知道小宇宙的 URL Scheme 到底是什么。于是便开始了我的 Hack 之旅,也就找到了小宇宙的这个安全漏洞。

    1. 找到小宇宙的 URL Scheme

    由于 Scheme 是在 App 中定义的,所以当我想到要找 Scheme 之后,第一反应的是去拿 iOS App 的 Info.plist(因为 iOS 是将 Scheme 定义在 info.plist 当中)。

    过去需要通过 IPA 备份、越狱等方式来获取到这个文件,不过得益于 M 系列支持在 macOS 上运行的原因,现在 IPA 的获得变得非常的简单。 先在 App Store 安装小宇宙,并在「应用程序」中找到小宇宙。

    image

    然后右键点击小宇宙,点击「显示包内容」

    image

    然后看到这样的内容,WrappedBundle 是一个假的应用程序,所以继续点击 Wrapper 往里跳转。

    image

    然后会发现里面还有一个 Podcast 应用,这个才是真正的小宇宙的 IPA 包。然后继续点击「显示包内容」

    image

    在包内容当中可以看到 info.plist 文件,使用 Xcode 或者 VSCode 打开 info.plist 文件。

    image

    info.plist 文件中,搜索 CFBundleURLSchemes ,找到了小宇宙的 URL Scheme(里面有很多个,但很多都是其他应用的,试一下就可以发现):cosmos://

    image

    2. 找到 URL Scheme 能够打开的页面

    找到了 URL Scheme,只能通过 cosmos:// 打开应用首页,无法满足我的需求,于是开始继续寻找可能的 URL Scheme 。一般来说,这个时候就只能继续反编译 IPA 包或者 APK 包了。不过对于我来说,这些不是一个好的选项(成本太高)。

    然后想到,小宇宙的网页似乎是提供了打开客户端的能力,所以可以从网页版找到突破口。通过简单的搜索,果然让我在网页前端找到了突破口。找到了 7 个 Scheme 。

    image

    当然,中间存在一些重复的 Scheme。所以最终梳理出来的 Scheme URL:

    • cosmos://page.cos/discover:打开发现页
    • cosmos://page.cos/shownotes/EPISODE_ID:打开节目的 Shownote 页面
    • cosmos://page.cos/episode/EPISODE_ID:打开节目的详情页
    • cosmos://page.cos/webView?url=:意义不明,看起来像是打开一个特定的 URL
    • cosmos://page.cos/web?url=:意义不明,看起来像是打开一个特定的 URL

    3. 发现问题 URL Scheme

    前面的三个很正常,但后面的两个带 URL 的引起我的注意 —— as a Hacker,你知道的,任何一个可能的输入框都可能成为我们的注入点,于是乎,我就构建了一个链接,来打开我的 Blog

    cosmos://page.cos/web?url=https%3A%2F%2Fwww.ixiqin.com
    Code language: JavaScript (javascript)

    将这个链接使用 Safari 打开,就会自动唤起小宇宙,并打开我的播客。

    至此,我发现了小宇宙这个跳转注入漏洞,并快速将其反馈给小宇宙官方同学。

    复盘:如何规避这样的问题

    在这个 Case 当中,小宇宙因为没有设置 URL 跳转的白名单,导致实际上出现了跳转恶意网站的风险。理论上,作为应用提供商,出于安全合规的视角,最好是控制 URL 跳转的域名和空间,避免被恶意滥用。

    或者也可以参考我们现在见到的很多网站,在外部跳转时加一个风险提醒

    如果在这个 Case 当中,小宇宙在一开始就限制了可以打开有限域名,那也不会出现如今我这次的漏洞问题。

    这个问题风险大么?

    取决于如何定义和如何使用。如果只是跳转一些常规网站,自然是风险不大的。但如果不受限制的,比如跳转到一些诈骗网站,可能风险就是大的。

    如何修改 Advanced Media Offloader 使其可以批量将历史文件上传至对象存储?

    Advanced Media Offloader 提供了 Bulk Offload 功能,可以实现将历史的文件上传到对象存储中,从而降低本地的存储压力,使得站点自身变得无状态。

    但其默认的 Bulk Offload 功能每次只能加载 50 个图片,如果附件太多,则需要点击 N 次,十分麻烦。

    image 6


    不过,可以通过简单的修改,来实现一次上传,将多个图片进行 Offload。

    在 WordPress 后台的插件管理器中,找到 Advanced Media Offloader 插件,并将includes/BulkOffloadHandler.php文件打开,找到其中的 get_unoffloaded_attachments 函数,修改函数定义中的 $batch_size = 50 为你想要的大小即可使其一次批量加载多个文件了。

    image 8
    修改位置

    修改后效果:

    image 7

    WordPress 使用 Caddy 完成静态化缓存

    使用 Caddy 处理 WordPress 当中,我提到在用 Caddy 处理 WordPress,且为了性能做了很多优化。

    我的博客经历了三重优化:最基础的 PHP OpCache + Redis 数据查询缓存 + 静态化缓存。

    其中一个比较有效的,便是在整个站点上加入静态化缓存,绝大多数游客看到的其实是预先生成好的静态页面,从而减少了数据库加载、渲染、计算的成本。

    而想要实现静态化,则需要借助于 Cache Enabler 插件和 Caddy 配置来完成。

    安装插件并启用

    安装 Cache Enabler 插件,并启用插件,启用后,在后台设置中,配置过期时间和对应的清除策略,并保存。这个时候,Cache Enabler 就会自动帮你去生成不同的页面了。

    image 5

    配置 Caddy 路由转发

    首先,你应该在你的 php_fastcgi unix//run/php/php-fpm.sock 前面加入缓存的代码并重启 Caddy,具体如下

    image 4

    缓存配置如下

         @cache {
    		not header_regexp Cookie "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in"
    		not path_regexp "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(index)?.xml|[a-z0-9-]+-sitemap([0-9]+)?.xml)"
    		not method POST
    		not expression {query} != ''
        }
    
        route @cache {
            try_files /wp-content/cache/cache-enabler/{host}{uri}/https-index.html /wp-content/cache/cache-enabler/{host}{uri}/index.html {path} {path}/index.php?{query}
        }
           
    Code language: JavaScript (javascript)

    这部分配置先定义了一个 @cache 块,用于后续判断,并在其中加入了多种条件判断,说明了不使用缓存的情况:

    • 如果用户有以下 Cookie,就不使用缓存:
      • comment_author(评论作者)
      • wordpress_[a-f0-9]+ (WordPress 的会话 Cookie)
      • wp-postpass(密码保护文章的 Cookie)
      • wordpress_logged_in(登录状态的 Cookie)
    • 如果当前请求命中了以下路径则不缓存
      • /wp-admin/(后台管理页面)
      • /xmlrpc.php(XML-RPC 接口)
      • 所有 wp-*.php 文件(WordPress 系统文件)
      • /feed/(RSS 订阅)
      • sitemap 相关文件
    • POST 请求不缓存(比如评论)
    • 带查询参数的缓存不请求。

    随后,通过 route @cache 定义了命中缓存部分的查找顺序:

    1. 先找 HTTPS 版本的缓存:/wp-content/cache/cache-enabler/{host}{uri}/https-index.html
    2. 再找普通缓存:/wp-content/cache/cache-enabler/{host}{uri}/index.html
    3. 如果找不到缓存,就尝试原始路径:{path}
    4. 最后尝试 PHP 文件:{path}/index.php?{query}

    查看效果

    打开一个无痕窗口,访问你的网站,如果在 html 底部看到 <!-- Cache Enabler by KeyCDN @ Sat, 04 Jan 2025 03:05:34 GMT (https-index.html) --> ,则说明你已经成功启用静态化缓存了!

    参考资料

    silver mercedes benz emblem on blue surface

    使用 Caddy 处理 WordPress

    在用了很久的 Docker 托管 WordPress 后, 近期我把 Blog 迁移到了腾讯云的香港轻量云主机上,以获得更快的访问体验。在这次迁移后,出于 Hack 方便的目的,我将 Nginx 替换成了 Caddy。你目前访问的站点便是一个基于 Caddy 托管的 WordPress 站点。

    安装 Caddy

    安装 Caddy 的过程不需要太过赘述,遵循 Caddy 官方安装文档当中的指南安装即可。

    安装 PHP

    完成了 Caddy 的安装后,则是安装 PHP,这里我使用的是 ondrej 维护的仓库

    sudo add-apt-repository ppa:ondrej/php
    sudo apt update
    Code language: Bash (bash)

    执行上述命令安装 PPA 仓库,就可以继续执行 apt install php 来安装 php & 对应的版本。此外,记得安装相关的依赖包

    apt-get install php-fpm php-mysql php-curl php-gd php-mbstring php-common php-xml php-xmlrpc -y
    Code language: Bash (bash)

    配置 Caddy

    完成安装后,就可以正常来配置 Caddy 。得益于社区的集成和 Caddy 官方的支持,Caddy 配置 WordPress 的支持非常简单,可以直接使用 Caddyfile 格式来撰写。

    example.ixiqin.com { # 这个配置是给 example.ixiqin.com
    
        root * /data/caddy/example.ixiqin.com # 我的网站文件都放在 /data/caddy/xxx 下,/data 是我挂载的数据盘
    
        log { #日志配置
            output file /var/log/caddy/example.ixiqin.com.log  # 日志路径
            format console # 日志格式
        }
    
        request_body { # 请求体大小
            max_size 20MB # 最大 20MB
        }
    
        encode zstd gzip # 支持 gzip 和 zstd 压缩
        file_server # 直接提供静态文件(比如图片啥的)
        php_fastcgi unix//run/php/php-fpm.sock # 使用 php_fastcgi 调用 php-fpm 来处理动态 php 文件。
    }
    Code language: PHP (php)

    只需要这样的配置,你就可以完成一个最基础的 WordPress 的站点的配置。

    其他配置

    对静态文件提供单独的 404 返回

    按照上面的配置,其实所有的请求都会转发给 php-fpm 来处理,从而造成额外的 PHP 资源浪费。因此,可以在配置当中加入如下代码,来让 Caddy 直接返回,从而避免对 PHP 性能的浪费。

    @static_404 {  
      path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
      not file  
    }  
    
    respond @static_404 "Not Found" 404 {  
      close  
    }
    
    Code language: JavaScript (javascript)

    配置缓存头

    除了静态文件的 404 处理,你还可以在 Caddy 当中配置静态文件的缓存,从而让浏览器更多的应用缓存,减少服务器的流量,提升加载速度。

    @static {  
      path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
    }  
    header @static Cache-Control "max-age=31536000,public,immutable"
    
    Code language: JavaScript (javascript)
    a computer screen with a bunch of text on it

    又准备折腾博客了

    我写博客经历过两个阶段 :

    1. 2012 ~ 2014 年:买虚拟主机、云主机,折腾性能和技术。在这个阶段,我的博客也经历过几轮变迁,也因为我自己没做好生产环境和测试环境的分离,导致数据丢了不少。目前的博客只能回溯到 2016 年便是因此。
    2. 2016 ~ 至今:不再折腾博客,开始专注于内容写作,并且保持每年都有更新,做一个活博客。

    写了十年,我也算是没少搞和 WordPress 相关的事情,不过,时间久了,脑子里那些工程师的想法,难免重新冒出来 —— 我是不是应该自己写一个 Blog 系统。对于曾经的我来说,可能是不容易的,我并没有那么丰富的开发经验。但对于如今的我来说,确实是不那么困难的事情。

    不过,写总是要有个目的和收益评估,不能「为了写而写」,而是应该有一个明确的目的,评估 ROI。

    我为什么想写?

    • 觉得 WordPress 还是太臃肿,有不少我用不上的能力。
      • 那么我自己的 Blog 系统应该有一个明确的 Feature List。这样才能避免需求无限的膨胀。
    • 我的需求已经逐步稳定下来,不太需要新的主题/插件了。
      • 这些年的确主要是更新内容,博客的形式、内容啥的,基本上都是稳定不变了。

    我为什么不能写?

    • 写 Blog 对于我来说没有什么特别的收益。
      • 毕竟这玩意没办法卖,除非写的过程中有别的收益,可能还好,不然大概率亏本。

    我如果要写一个 Blog 系统,我需要哪些 Feature?

    • 文章系统:包含标题、描述、内容、Slug、目录、标签几个核心属性和实体。
      • Slug 需要自动翻译:我特别依赖这个功能。懒得自己翻译 Slug
    • 支持图片自动上传 S3
      • 我现在的图片都是用的外链,这样服务器自身的压力没那么大。
    • 支持评论
      • 评论还需要能够自动发送邮件更新。
      • 评论要能实现反垃圾。
      • 历史的 600+ 评论可导入
    • 简洁稳定的主题设计
      • 坦白来讲,这几年我很少做主题方面的变更了,基本上就是在几个景点主题上来回切换。
    • 支持 RSS Feed 等能力
    • 支持在线编写(可能非必需,最近开始逐步用 Ulyssess 写作,其实对于网页写文章的诉求越来越小了,maybe Hexo 是可接受的方案。)
    a computer screen with a drawing of two people talking to each other

    Solution as a Service, not Software as a Service

    在软件领域,我们有非常经典的 IaaS、PaaS、SaaS 模型,我们使用这个模型定义着我们的产品。

    但另一方面,这个定义也局限了很多人的想法 —— SaaS just Software as a Service。

    实际上,如果你是一个独立开发者 / 直面用户的岗位,你需要深刻的明白 —— SaaS 更大的价值不应该是 Software ,而应该是 Solution —— 用户从来不关心你是不是有 Software,用户只关心你的 Solution 能不能解决你的问题。

    如果你不知道你的 Software 是在解决谁的问题 —— 不妨想想,是不是你没有找到你的Software 到底应该如何放在 Solution 当中 ,以及那个 Solution 对应的问题到底是什么。

    持续关注用户的问题,尝试提供靠谱的 Solution 去解决他的问题 —— 而不是关注你的 Software。除了你,没有人真正在意你的 Software。

    brown and black round board

    OKR 要长远,但迭代要敏捷

    飞书执行季 OKR 已经很久了,相比于过去的双月 OKR,我认为这确实是一个好的事情。季度 OKR 可以让我们在一个更长期的事情上来完成我们要达成的目标而无需担心自己所做的事情要更加的长远和长期,也期待更多的团队和协作方可以享受到季 OKR 的带来的长期。

    但从另外一个方向来看,即使我们使用了季度 OKR,也需要关注执行的迭代。

    OKR周期是我们达成目标的周期,而做事的手段则应该尽可能的敏捷和快速。快速判断、快速执行、快速复盘、快速修正。

    目标大和周期长是我们要着眼于更重要、更长期的事情。而迭代的敏捷,则可以帮助我们更好更快的抵达目标。