JS编写仿Ant Design的消息提示框

最近在改一个项目,前端使用了Ant Design且我没有源码,因此只能自己实现一个MessageBox组件。
在掘金上看见了一个现成的组件,略加修改使其适应Ant Design的风格。
用法:

1
2
3
4
5
const message = useMessage();
message.success("success");
message.error("error");
message.info("info");
message.warning("warning");

代码如下:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/**
* 消息提示条
* @param {object} params
* @param {number} params.duration 持续时间(毫秒),默认`3000`
* @param {number} params.zIndex 起始定位层级,默认`1000`
*/
function useMessage(params = {}) {
const doc = document;
const cssModule = `__${Math.random().toString(36).slice(2, 7)}`;
const className = {
box: `msg-box${cssModule}`,
hide: `hide${cssModule}`,
text: `msg-text${cssModule}`,
icon: `msg-icon${cssModule}`
}
const style = doc.createElement("style");
style.textContent = `
.${className.box}, .${className.icon}, .${className.text} {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.${className.box} {
position: fixed;
top: 0;
left: 50%;
display: flex;
padding: 12px 16px;
border-radius: 2px;
background-color: #fff;
box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);
white-space: nowrap;
animation: ${className.box}-move .4s;
transition: .4s all;
transform: translate3d(-50%, 0%, 0);
opacity: 1;
overflow: hidden;
padding: 9px 12px;
margin: 0;
box-sizing: border-box;
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
pointer-events: all;
}
@keyframes ${className.box}-move {
0% {
opacity: 0;
transform: translate3d(-50%, -100%, 0);
}
100% {
opacity: 1;
transform: translate3d(-50%, 0%, 0);
}
}
.${className.box}.${className.hide} {
opacity: 0;
transform: translate3d(-50%, -100%, 0);
}
.${className.icon} {
display: inline-block;
width: 16px;
height: 16px;
border-radius: 50%;
overflow: hidden;
margin-right: 6px;
position: relative;
align-items: center;
color: inherit;
font-style: normal;
line-height: 0;
text-align: center;
text-transform: none;
vertical-align: -0.125em;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.${className.text} {
font-size: 14px;
line-height: 18px;
color: #555;
}
.${className.icon}::after,
.${className.icon}::before {
position: absolute;
content: "";
background-color: #fff;
}
.${className.box}.info .${className.icon}, .${className.box}.info::after {
background-color: #1677ff;
}
.${className.box}.success .${className.icon}, .${className.box}.success::after {
background-color: #52c41a;
}
.${className.box}.warning .${className.icon}, .${className.box}.warning::after {
background-color: #faad14;
}
.${className.box}.error .${className.icon}, .${className.box}.error::after {
background-color: #ff4d4f;
}
.${className.box}.info .${className.icon}::after,
.${className.box}.warning .${className.icon}::after {
top: 15%;
left: 50%;
margin-left: -1px;
width: 2px;
height: 2px;
border-radius: 50%;
}
.${className.box}.info .${className.icon}::before,
.${className.box}.warning .${className.icon}::before {
top: calc(15% + 4px);
left: 50%;
margin-left: -1px;
width: 2px;
height: 40%;
}
.${className.box}.error .${className.icon}::after,
.${className.box}.error .${className.icon}::before {
top: 20%;
left: 50%;
width: 2px;
height: 60%;
margin-left: -1px;
border-radius: 1px;
}
.${className.box}.error .${className.icon}::after {
transform: rotate(-45deg);
}
.${className.box}.error .${className.icon}::before {
transform: rotate(45deg);
}
.${className.box}.success .${className.icon}::after {
box-sizing: content-box;
background-color: transparent;
border: 2px solid #fff;
border-left: 0;
border-top: 0;
height: 50%;
left: 35%;
top: 13%;
transform: rotate(45deg);
width: 20%;
transform-origin: center;
}
`.replace(/(\n|\t|\s)*/ig, "$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/ig, "$1").replace(/(\{|\}|\,|\:|\;)\s/ig, "$1");
doc.head.appendChild(style);
/** 一直累加的定位层级 */
let zIndex = params.zIndex || 1000;
/**
* 消息队列
* @type {Array<HTMLElement>}
*/
const messageList = [];

/**
* 获取指定`item`的定位`top`
* @param {HTMLElement=} el
*/
function getItemTop(el) {
let top = 8;
for (let i = 0; i < messageList.length; i++) {
const item = messageList[i];
if (el && el === item) {
break;
}
top += item.clientHeight + 20;
}
return top;
}

/**
* 删除指定列表项
* @param {HTMLElement} el
*/
function removeItem(el) {
for (let i = 0; i < messageList.length; i++) {
const item = messageList[i];
if (item === el) {
messageList.splice(i, 1);
break;
}
}
el.classList.add(className.hide);
messageList.forEach(function(item) {
item.style.top = `${getItemTop(item)}px`;
});
}

/**
* 显示一条消息
* @param {string} content 内容
* @param {"info"|"success"|"warning"|"error"} type 消息类型
* @param {number} duration 持续时间,优先级比默认值高
*/
function show(content, type = "info", duration) {
const el = doc.createElement("div");
el.className = `${className.box} ${type}`;
el.style.top = `${getItemTop()}px`;
el.style.zIndex = zIndex;
el.innerHTML = `
<span class="${className.icon}"></span>
<span class="${className.text}">${content}</span>
`;
zIndex++;
messageList.push(el);
doc.body.appendChild(el);
// 添加动画监听事件
function animationEnd() {
el.removeEventListener("animationend", animationEnd);
setTimeout(removeItem, duration || params.duration || 3000, el);
}
el.addEventListener("animationend", animationEnd);
function transitionEnd() {
if (getComputedStyle(el).opacity !== "0") return;
el.removeEventListener("transitionend", transitionEnd);
el.remove();
}
el.addEventListener("transitionend", transitionEnd);
}

return {
show,
/**
* 普通描述提示
* @param {string} msg
*/
info(msg) {
show(msg, "info");
},
/**
* 成功提示
* @param {string} msg
*/
success(msg) {
show(msg, "success");
},
/**
* 警告提示
* @param {string} msg
*/
warning(msg) {
show(msg, "warning");
},
/**
* 错误提示
* @param {string} msg
*/
error(msg) {
show(msg, "error");
}
}
}

如何正确配置 Cloudflare,给你的博客加速

前言

本文完全基于正常NS接入Cloudflare的流程进行讲解,因此不含优选内容。

配置

缓存

好的缓存配置能让回源流量少很多,也能节省客户端的加载时间。
Cloudflare缓存配置页,将缓存级别设置为忽略查询字符串
浏览器缓存 TTL按需设置,一般设置5天以上。
讲解:
缓存级别的三个选项分别如下解释:

  • 没有查询字符串:一旦静态资源被带上URL参数访问,就回源。
  • 忽略查询字符串:对任何URL参数都返回同一个资源。
  • 标准:对不同URL参数进行不同的缓存。

为什么这么设置:一般的,静态资源不同的URL参数请求取得的结果是相同的,而且设置为标准没有查询字符串有被攻击的风险(对不同参数的资源会进行回源)。
浏览器缓存 TTL:静态资源在浏览器缓存的时长,按照静态资源的变化情况来设置。
如果有条件,还可以用API设置边缘缓存时长,与浏览器缓存 TTL一样,按需设置,一般来说时间长一点较好。
缓存设置

速度

侧边栏“速度-优化”页面。建议全部开启即可。
在“内容优化”中,关闭Rocket Loader。将Auto Minify全部打开。

安全

这里只讲基本的安全配置,不讲自定义规则。
“安全性-自动程序”中,开启自动程序攻击模式
“安全性-设置”中,开启浏览器完整性检查。其他两项按需设置。
建议使用API关闭Privacy Pass(我没找到设置项,可能被CF移除了),因为它会导致一部分验证码绕过漏洞。

Hytron HK BGP Standard 测评

配置

这台服务器是黑五入手的,详细配置如下:

项目 信息
CPU數量 1vCPU $2.52 USD
内存(MB) 1024MB (1GB) $2.52 USD
硬盤空間(GB) 10GB $4.20 USD
流量(GB) 500GB (0.5TB) $4.20 USD
帶寬速率(Gbps) 7Gbps
1
2
3
4
5
6
7
8
9
10
11
12
13
-> System Information

CPU Model Name: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
CPU Cache Size: L1: 64.00 KB / L2: 4.00 MB / L3: 16.00 MB
CPU Specifications: 1 vCPU(s)
Virtualization Ready: Yes (Based on Intel VT-x, Nested Virtualization Enabled)
Virtualization Type: KVM
Memory Usage: 150.04 MiB / 981.21 MiB
Swap Usage: [ no swap partition or swap file detected ]
Disk Usage: 2.05 GiB / 9.78 GiB
Boot Disk: /dev/vda1
OS Release: Ubuntu 20.04 LTS (x86_64)
Kernel Version: 5.4.0-29-generic

叠了黑五半价券之后,年付大概 70¥

常规参数

CPU 硬盘跑分
网络速度
回程线路

DDoS 防护

我是专门为了它的 CFMT 线路接入购买的,在测评时也测试了一下它的DDoS防护。
测试使用的攻击平台由我的朋友提供,为了防止被滥用就不公开了。
第一次测试 UDP 反射攻击。攻击带宽大约 700Gbps。丢包率如下图(前三分之一为未开始攻击时的丢包率,后三分之二为攻击时)
UDP反射
第二次测试 SYN Flood 攻击。攻击带宽大约 50Gbps。所有外网流量都无法流入,但停止攻击后立刻复活。

从基础数据来看, CPU 单核跑分 852 分是在一个还说得过去的水平的,对于一台普通的高防网站服务器来说够用了。硬盘 IO 在 VPS 范围呢则是比较高的一个水平,可惜硬盘太小了,加大容量得加钱。
网络带宽没有续表,但是由于其三网绕日本 NTT 的线路导致到国内带宽缩水很多。我加钱上的 7Gbps 到国内仅剩 1Gbps 了。
我购买这台机器主要是为了它的 DDoS 防护。目前来看它的防护确实耀眼。得益于Cloudflare Magic Transmit的加持,使其能够防护超过 5Tbps 的 DDoS 攻击,我个人认为是比较满意的了。

Typecho 登录返回 302 的解决方法

之前学校要开课就拿 Typecho 糊了一个小网站拿来配合,今天发现登不上去了感觉非常离谱。
F12 抓包看了一下发现登录返回了 302 而且没有附带报错信息,非常非常的离谱。
查了一下文档,没有查到(不过也正常,ty 文档约等于空气)。
研究了一下发现:
修改 Typecho 根目录下的config.inc.php`,添加这么一行:

1
define('__TYPECHO_SECURE__', true);

然后清除一下缓存,他就好了。
总之就是非常离谱

修复跨域 (CORS) 问题

如果你在进行前后端分离的开发时发现在测试环境内没有任何问题,而一上生产环境就会出现前端根本无法向后端发出请求的问题,那么恭喜你,你成为了万恶的CORS的受害者之一!!!
你可以按F12打开开发者工具,选择网络选项卡,按F5刷新网页。不出意外的话你会看见一长串的CORS Error。同时,控制台也会输出错误。

什么是 CORS

@> 跨源资源共享 (CORS)(或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的”预检”请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
跨源HTTP请求的一个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json 的请求。
出于安全性,浏览器限制脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
@>
@> 来源: 跨源资源共享(CORS) - HTTP | MDN

如何修复

如果你使用了支持修改返回头的CDN,你可以直接在CDN侧配置。
CDN侧添加如下四个返回头:

标头名称
Access-Control-Allow-Methods GET,POST,HEAD,OPTIONS
Access-Control-Allow-Origin *
Cross-Origin-Opener-Policy cross-origin
Cross-Origin-Resource-Policy cross-origin
如果你没有使用CDN,那么直接在Nginx配置文件中写规则即可。

使用 CloudFlare Workers 反向代理 Gravatar

前言

博主非常不建议用这种方法自建你的 Gravatar 源。因为 Cloudflare 由于被国内某些小脑萎缩群体用来搭建代理之后,连接延迟大大增加!

方法

科赋锐控制面板,找到左边菜单的Workers
依次点击创建应用程序 -> 创建Worker
随便写一个名称,然后部署。
编辑代码,替换成如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
addEventListener("fetch", event => {
let url = new URL(event.request.url);
url.hostname = "secure.gravatar.com";

let request = new Request(url, event.request);
event.respondWith(
fetch(request, {
headers: {
'Referer': 'https://secure.gravatar.com/',
'User-Agent': 'Cloudflare Workers'
}
})
);
});

大功告成!

【瞎折腾】自建支持分线路解析的权威域名服务器

[post cid=”21” /]
接上文,使用 PowerDNS 搭建根服务器后,我又想尝试搭建权威域名服务器。

首先说一下我的观点:自建权威域名服务器犀利而无用。他要求高昂的成本但几乎没有回报。我的权威域名服务器是在两台我弃用的服务器上搭建的,因此可以无视成本。但我不建议各位这么做。

利用 PowerDNS 的 Lua 支持,我们可以轻松的搭建出一套支持分线路解析的 GeoDNS 服务器。
需要:

  • 一个 Maxmind 账户
  • 至少两台 VPS 服务器(目前域名服务商要求最少需要两个权威域名服务器)

配置地理信息库

  1. Maxmind 官网 获取账户的密钥,需要密钥来更新数据库。
  2. 安装geoipupdate 工具。
1
apt install geoipupdate
  1. 创建文件夹/etc/geoip,并修改/etc/GeoIP.conf
1
2
3
4
AccountID [账户编号]
LicenseKey [授权密钥]
EditionIDs GeoLite2-ASN GeoLite2-City GeoLite2-Country
DatabaseDirectory /etc/geoip
  1. 用 Cron 设置上自动更新:
1
2
3
crontab -e
# 加上一行:
0 0 * * 0 /usr/bin/geoipupdate
  1. 安装 GeoIP 后端
1
apt install -y pdns-backend-geoip
  1. 修改配置文件/etc/powerdns/pdns.conf
1
2
3
enable-lua-records=yes
launch=gmysql,geoip
geoip-database-files=/etc/geoip/GeoLite2-City.mmdb

配置 GeoDNS

在 PowerDNS Lua 中,用户的 IP 地址作为一个变量bestwho存在。不过如果用户的 DNS 服务器没有告知上游服务器用户的地址,那么bestwho就会指向 DNS 服务器的地址。
PowerDNS 提供了一个 lua 函数pickclosest,该函数从一个 IP 列表中选择离bestwho地理位置最近的服务器返回。
还有一个函数country,可以分辨国家。
两者的使用:

1
2
A "pickclosest({'1.1.1.1','8.8.8.8','114.114.114.114','223.5.5.5'})"
A ";if(country('CN')) then return '223.5.5.5' else return '1.1.1.1' end"

自建 DNS 根服务器

前言

今天晚上博主突发奇想:既然根域名服务器的存根文件是公开的,那为什么不自建一个呢?使用现有的资源,我们完全可以在短时间内自己搭建一个 DNS 根服务器,并且让你的递归 DNS 使用它。

DNS 解析原理

  1. 你打开电脑上的浏览器,输入了https://www.baidu.com/想要访问网页。此时你的浏览器会将你输入的 URL 中的域名取出,并根据你设置的系统 DNS 请求到域名服务器。
  2. 域名服务器检查自身缓存是否存在www.baidu.com.的记录,如存在则直接返回,如不存在则继续请求下去。
  3. 域名服务器向根域名服务器发出query www.baidu.com.请求,根服务器返回了.com顶级域名的顶级域名服务器地址a.gtld-servers.net.
  4. 域名服务器向顶级域名服务器发出query www.baidu.com.请求,得到baidu.com权威域名服务器地址dns.baidu.com
  5. 域名服务器向权威域名服务器发出query www.baidu.com.请求,得到www子域的解析记录。
  6. 重复以上步骤,直到得到可被用于请求的 IP 地址。并将其存入缓存后返回给客户端。如无记录,则根据域名状态返回错误码。
  7. 你的浏览器得到了 IP地址,向它发送请求。服务器返回页面。

到上述第六步时,DNS 解析过程就已经完成了。看似复杂,实则只在几十毫秒内。

自建 DNS 根服务器

目前可选的 DNS 服务器程序很多,例如广为人知的PowerDNSBIND9。本文选择了PowerDNS作为服务器程序。

  1. 安装 PowerDNS: apt install pdns-server pdns-backend-mysql
  2. 修改它的配置文件,使其支持BIND格式的 zone 文件。
    vim /etc/powerdns/pdns.d/bind.conf
1
2
3
4
launch=bind
bind-config=/etc/powerdns/bind.conf
bind-check-interval=60
bind-ignore-broken-records=yes
  1. IANA 下载 root.zone 文件。
  2. 对 root.zone 作一些修改:
  • 删除所有第 1 列是.,并且第 4 列是NS的行。
  • 在最开头添加以下三行:
1
2
.                       86400   IN      NS      root-server.你的域名.
root-server.你的域名. 86400 IN A 你的服务器IP

注意:所有的域名后面的.不可省略!

  • 删除所有第 4 列不是 A,AAAA,SOA,NS 或者 DS 的行(这些记录与 DNSSEC 有关,而我们的更改会破坏 DNSSEC,因此直接删除即可)。
1
2
mv root.zone root.zone.bak
cat root.zone.bak | awk '{if ($4=="A" || $4=="AAAA" || $4=="SOA" || $4=="NS" || $4=="DS") print $0}' > root.zone

如果有多台服务器要成为根服务器,重复以上步骤几次即可。
5. 新建/etc/powerdns/bind.conf文件,添加如下内容。

1
zone "." { type native; file "/etc/powerdns/root.zone"; };
  1. systemctl restart pdns重启 PowerDNS 服务器。

验证你的成果

  1. 运行dig @你服务器 IP,应当会得到如下回复(其中root-server.omn.cc是我设置的域名):
    结果
  2. 运行dig @你服务器IP www.example.com,得到如下回复:

    ;; AUTHORITY SECTION:
    com. 172800 IN NS m.gtld-servers.net.
    com. 172800 IN NS l.gtld-servers.net.
    com. 172800 IN NS k.gtld-servers.net.
    ……

    ;; ADDITIONAL SECTION:
    m.gtld-servers.net. 172800 IN AAAA 2001:501:b1f9::30
    m.gtld-servers.net. 172800 IN A 192.55.83.30
    l.gtld-servers.net. 172800 IN AAAA 2001:500:d937::30
    l.gtld-servers.net. 172800 IN A 192.41.162.30
    ……

恭喜!你的根服务器已经搭建好了!

使用你的根服务器来进行递归解析

有了根服务器,接下来就可以配置你的递归 DNS 服务器,让它们使用你自己的根服务器完成解析了。
将你在root.zone文件里自己添加的两行/三行(如果你有IPV6)内容保存为roots.hint文件,传到你的递归 DNS 服务器上去。
然后在 PowerDNS Recursor 的配置文件中新加一行:

1
hint-file=/etc/powerdns/root.hint

如果你开了 DNSSEC 验证,还需要添加一行:

1
lua-config-file=/etc/powerdns/script.lua

然后在/etc/powerdns/script.lua中写入:

1
addNTA(".")

保存后重启 PowerDNS Recursor 即可。

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×