如何检测用户启动广告屏蔽插件

广告屏蔽插件让我们作为用户可以减少广告的打扰,但是作为开发者有时候因为用户开启了广告屏蔽导致网站收入减少或者影响了一部分功能的使用。我们不得不提示用户关闭广告插件,以正常使用网站。 但是针对不同的需求,广告的检测方式也不一样。我们接下来详细讲一讲。

# 网站本身投放广告

针对网站本身有投放广告的情况下,我们可以通过 JavaScript 来检查广告的 HTML 元素是否存在,以及 CSS 属性来判断。示例代码如下:

function isDisplayNone(node: Element | null): boolean {
  if (!node) {
    return false;
  }
  if (getComputedStyle(node).display === 'none') return true;
  return isDisplayNone(node.parentElement);
}

function isHidden(node: Element) {
  if (getComputedStyle(node).visibility === 'hidden') return true;
  if (isDisplayNone(node)) return true;

  return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过检测广告元素是否被删除或者隐藏可以非常可靠的提示用户关闭广告插件或者其他操作。

# 网站由于广告屏蔽导致功能不全

例如由于用户开启广告屏蔽插件,导致支付服务 stripe 无法正常使用。我们本身并没有在网站上发布广告。这时候可以使用网络请求的方式来检测广告屏蔽插件。示例代码如下:

  checkAdBlocker() {
   fetch('https://pagead2.googlesyndication.com/pagead/show_ads.js', {
     mode: 'no-cors'
   }).catch(() => {
     console.log('Please disable ad blockers');
   })
  }
1
2
3
4
5
6
7

原理很简单,广告屏蔽插件一定会阻止加载谷歌的 JavaScript 代码来屏蔽广告的执行,只要我们使用 fetch 方法来请求这个 JavaScript 文件,如果出错那就一定是使用了广告屏蔽插件。 所以同样的道理,如果因为有些图片的路径带上了 ad 之类的关键字导致被屏蔽,我们也可以使用这个方法来进行检测。记住这里需要开启 no-cors 模式,因为 JavaScript 的网络请求是受到同源策略的严格控制的。

# 总结

广告屏蔽插件的原理基本有三种,第一是有列表黑名单,出现在列表黑名单的资源都会直接被中断网络请求,例如无法加载图片,JavaScript 文件等等。第二是关键字匹配,如果资源文件里面有 ad 之类的相关广告关键字,那这个资源也会被中断请求。第三个是 DOM 检测,针对已经渲染好的 HTML 文件,通过 class 属性来检测是否有相关的广告关键字。如果符合结果,就删除相应的元素。

# 参考

FuckAdBlock (opens new window)

ELK收集Docker项目的日志

现在主流的项目部署都是采用 docker 部署了,一般项目使用 STDOUT 输出的日志都被 docker 来收集和保存。我们需要使用著名的 ELK (opens new window) 来分析日志,所以如何部署 ELK 并且把 docker 的日志转发给 ELK 呢? 这就是本篇文章我们需要解决的问题。

# 搭建 ELK

既然我们使用 docker 进行部署,那我们当然也要使用 docker 部署 ELK。docker-elk (opens new window) 是一个配置好的项目,我们可以基于这个项目迅速搭建起一个 ELK 项目。说实话, ELK 项目非常消耗内存,所以至少保证机器有 4GB 内存,否则可能会宕机。搭建好 ELK 之后,记得按照教程重新设置密码。

$ docker-compose exec -T elasticsearch bin/elasticsearch-setup-passwords auto --batch
1

初始化密码之后,并修改 kibanalogstash 的配置文件更新密码。最后再重启这两个服务:

$ docker-compose restart kibana logstash
1

# 使用 GELF 收集日志

GELF (opens new window) 是一个经典的日志格式并且 docker 原生支持这个格式,首先我们需要配置 logstash 采取这个格式来收集日志。

修改 logstash/pipeline/logstash.conf 文件的 input 部分:

input {
	beats {
		port => 5044
	}

	gelf {
		port => 5000
	}
}
1
2
3
4
5
6
7
8
9

这样收集 gelf 的端口就是 5000 默认使用 UDP 格式。修改配置之后别忘了重启 logstash

$ docker-compose restart logstash
1

接下来实际项目要把日志转发到 ELK 上,如果我们使用 docker-compose.yml 需要在当前的 service 上添加 logging 相关的配置:

logging:
  driver: gelf
  options:
    gelf-address: 'udp://0.0.0.0:5000'
1
2
3
4

如果你的 ELK 和 实际项目并不是在同一台主机上,记得修改 0.0.0.0 成相应的主机名或 IP 地址。这样配置之后就可以在 kibana 上查看收集到的日志了。 不过要记得配置 Index patterns 然后在 Discover 界面上就能看到日志记录了。

# 总结

ELK 收集 docker 日志并不是很复杂,但是摸索的时候很难调试,例如 ELK 配置了 gelf 方法收集日志,但却找不到好的方法来测试这个接口是否正常。 我只是通过 nmap 命令来检测 5000 端口是否开放,并不知道是否正常运行。

扫描当前主机的 UDP 端口:

nmap -sU 0.0.0.0
1

启动项目的 docker 的时候还出现了警告信息 warning: no logs are available with the 'gelf' log driver 为了这个问题我查了半天,但发现 这个信息即使出现了也可以正常收集。浪费了不少时间。最终的解决方案总是很简单,但查找的过程中难免绕弯路。

# 参考

https://ibm-cloud-architecture.github.io/b2m-nodejs/logging/ (opens new window)

Using Free Let’s Encrypt SSL/TLS Certificates with NGINX (opens new window)

Django使用CKEditor的相关经验

最近在使用 Django 进行后台管理系统的开发,其中有需求要使用所见即所得的富文本编辑器来让用户上传图片和文字并排版。这里我们选择了CKEditor (opens new window),原因很简单,因为这个第三方库提供了很好的 AWS S3 的支持,而我们目前也确实把静态资源例如图片放在 S3 上。

# 配置 AWS S3

我们一般使用django-storages (opens new window)这个库来集成 AWS S3, 首先需要在设置里面配置 AWS 的 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_S3_REGION_NAME

然后自定义一个 Storage class 方便配置,代码如下:

class MaterialStorage(S3Boto3Storage):
    bucket_name = 'material-bucket'
    default_acl = 'private'
1
2
3

默认都是私有桶,避免用户拿到图片的 URL 就可以分享给别人。S3 默认开启 AWS_QUERYSTRING_AUTH, 访问资源的格式类似下面这种带有查询参数:

https://s3.amazonaws.com/photos/puppy.jpg?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D&Expires=1175139620
1

默认的查询参数认证有效期是一个小时,也就意味着这个链接最多一个小时有效,而且认证参数最大只能设置一周有效。可以有效避免链接泄漏导致的资源泄漏风险。

# 配置项目导入 CKEditor

按照官方指导可以很顺利的导入 CKEditor:

from django.db import models
from ckeditor.fields import RichTextUploadingField

class Post(models.Model):
    content = RichTextUploadingField()
1
2
3
4
5

# CKEditor 配置 S3

官方教程上说明必须关掉AWS_QUERYSTRING_AUTH (opens new window)才能正常使用。但是关掉之后就意味着任何人只要获得资源的链接就可以访问,这是很危险的。所以我们如何保证开启认证参数的同时又能正常使用编辑器呢?这个才是本文的重点。

在开启认证参数的时候,编辑器保存的是当时上传照片时候的认证参数,也就意味着一个小时之后再访问的时候,就没法正常显示图片。如何保证页面载入编辑器的时候,保证图片正常显示呢?

我的思路是在页面载入的时候读取编辑器里面的内容,采用正则表达式匹配存储在 AWS 上的图片,然后更新图片相关的认证信息。

# 数据库读入时自动更新认证信息

查看 Django 文档的时候发现,我们可以自定义一个 field,然后使用一个 hook 函数 from_db_value (opens new window)。在数据读入的时候进行相关操作,例如把存入的 JSON 字符串转换成 JSON 对象。通过这个方法,我们可以对数据进行加工,更新图片的认证信息。

class CKEditorField(RichTextUploadingField):
    def from_db_value(self, value, expression, connection):
        if value:
            return update_image(value)
        else:
            return value
1
2
3
4
5
6

我们自定义了一个 CKEditorField 字段,从数据库读出的时候进行图片认证信息的更新。update_image 函数的实现如下:

def update_image(content):
    storage = MaterialStorage()
    s3_regex = re.compile(r'"(https:\/\/amazonaws.com\S+)"', re.MULTILINE)
    links = s3_regex.findall(content)
    for link in links:
        match = re.search(r'\/upload\S+.(png|jpe?g)', link)
        if match:
            path = match.group()
            if storage.exists(path):
                content = content.replace(link, storage.url(path))
    return content
1
2
3
4
5
6
7
8
9
10
11

简单的解释一下代码功能,首先匹配 AWS 链接,然后匹配链接中的图片路径,知道图片路径之后。再使用 storage 对象查找是否存在,如果存在的话,获取这个资源的 URL。这时候新的 URL 就带有新的认证信息。通过这种方式,我们就能保证每次页面显示编辑器的时候,里面的图片都可以正常显示出来。

# 总结

知道答案之后,当然觉得这么做很简单。但是探索出这个答案却非常花时间,首先要明确解决问题的思路,然后顺着这个思路去想出可能的解决方案。最后,问题迎刃而解。

# 参考

Authenticating Requests: Using Query Parameters (opens new window)

File storage API (opens new window)