使用Cloudflare和notion搭建自己的博客

欢迎访问我的个人博客ximikang.icu

使用Cloudflare和notion搭建自己的博客

使用过Nation的小伙伴一定在某个时刻冒出过这样子的想法:这个笔记应用这么好用,而且是基于Web端开发的,那为什么不可以用他来作为我自己的博客呢?这样我是不是可以用记笔记的方式来快速的实现和维护自己的博客了呢?

我搭建的个人博客,欢迎大家访问。https://notion.ximikang.icu/

Untitled

购买一个域名并将域名转移到Cloudflare

首先我们需要将域名的域名服务器设置为 Cloudflare 的域名服务器,这样就可以使用 Cloudflare 的 DNS 服务了。

注册Cloudflare ,然后在这里添加自己的域名:

Untitled

将你的域名指向notion

切换到 dns 页面,添加一条 CNAME 记录,如果你自己的二级域名为 blog.xxx.com,那么名称那就填 blog,目标填 Notion 的域名,保存。

Untitled

Untitled

或者需要将主域名设置为则为

Untitled

注意需要将代理状态切换至已代理,这样才能用cloudflare的免费CDN。

添加后下方就会显示已添加的域名,点击已添加的域名进入设置页面。

配置Cloudflare的Web Worker

配置workder使得我们访问我们上面定义的http请求可以自动改写。

Untitled

  1. 首先管理Workers,新建一个对应的notion的worker。

    Untitled

    获取notion页面中的链接ID,如图中的id需要在worker配置中使用。

    Untitled

    创建worker后编辑worker,将如下代码复制到worker中。

    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
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    /* CONFIGURATION STARTS HERE */

    /* 添加域名 notion.ximikang.icu*/

    const MY_DOMAIN = '';

    /*
    添加刚才的id
    例如:
    '': 'bb1678c7e8fe47b29bf49aac08aebbb',
    */
    const SLUG_TO_PAGE = {
    '': '',
    };

    /* 添加主页面的标题和描述 */
    const PAGE_TITLE = '';
    const PAGE_DESCRIPTION = '';

    /* 可以选择显示的字体 https://fonts.google.com
    例如:const GOOGLE_FONT = 'Noto Sans Simplified Chinese';*/
    const GOOGLE_FONT = '';

    /* 添加自定义脚本,可以添加Google Analytics */
    const CUSTOM_SCRIPT = ``;

    /* CONFIGURATION ENDS HERE */

    const PAGE_TO_SLUG = {};
    const slugs = [];
    const pages = [];
    Object.keys(SLUG_TO_PAGE).forEach(slug => {
    const page = SLUG_TO_PAGE[slug];
    slugs.push(slug);
    pages.push(page);
    PAGE_TO_SLUG[page] = slug;
    });

    addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event.request));
    });

    function generateSitemap() {
    let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
    slugs.forEach(
    (slug) =>
    (sitemap +=
    '<url><loc>https://' + MY_DOMAIN + '/' + slug + '</loc></url>')
    );
    sitemap += '</urlset>';
    return sitemap;
    }

    const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    };

    function handleOptions(request) {
    if (request.headers.get('Origin') !== null &&
    request.headers.get('Access-Control-Request-Method') !== null &&
    request.headers.get('Access-Control-Request-Headers') !== null) {
    // Handle CORS pre-flight request.
    return new Response(null, {
    headers: corsHeaders
    });
    } else {
    // Handle standard OPTIONS request.
    return new Response(null, {
    headers: {
    'Allow': 'GET, HEAD, POST, PUT, OPTIONS',
    }
    });
    }
    }

    async function fetchAndApply(request) {
    if (request.method === 'OPTIONS') {
    return handleOptions(request);
    }
    let url = new URL(request.url);
    url.hostname = 'www.notion.so';
    if (url.pathname === '/robots.txt') {
    return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml');
    }
    if (url.pathname === '/sitemap.xml') {
    let response = new Response(generateSitemap());
    response.headers.set('content-type', 'application/xml');
    return response;
    }
    let response;
    if (url.pathname.startsWith('/app') && url.pathname.endsWith('js')) {
    response = await fetch(url.toString());
    let body = await response.text();
    response = new Response(body.replace(/www.notion.so/g, MY_DOMAIN).replace(/notion.so/g, MY_DOMAIN), response);
    response.headers.set('Content-Type', 'application/x-javascript');
    return response;
    } else if ((url.pathname.startsWith('/api'))) {
    // Forward API
    response = await fetch(url.toString(), {
    body: url.pathname.startsWith('/api/v3/getPublicPageData') ? null : request.body,
    headers: {
    'content-type': 'application/json;charset=UTF-8',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
    },
    method: 'POST',
    });
    response = new Response(response.body, response);
    response.headers.set('Access-Control-Allow-Origin', '*');
    return response;
    } else if (slugs.indexOf(url.pathname.slice(1)) > -1) {
    const pageId = SLUG_TO_PAGE[url.pathname.slice(1)];
    return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 301);
    } else {
    response = await fetch(url.toString(), {
    body: request.body,
    headers: request.headers,
    method: request.method,
    });
    response = new Response(response.body, response);
    response.headers.delete('Content-Security-Policy');
    response.headers.delete('X-Content-Security-Policy');
    }

    return appendJavascript(response, SLUG_TO_PAGE);
    }

    class MetaRewriter {
    element(element) {
    if (PAGE_TITLE !== '') {
    if (element.getAttribute('property') === 'og:title'
    || element.getAttribute('name') === 'twitter:title') {
    element.setAttribute('content', PAGE_TITLE);
    }
    if (element.tagName === 'title') {
    element.setInnerContent(PAGE_TITLE);
    }
    }
    if (PAGE_DESCRIPTION !== '') {
    if (element.getAttribute('name') === 'description'
    || element.getAttribute('property') === 'og:description'
    || element.getAttribute('name') === 'twitter:description') {
    element.setAttribute('content', PAGE_DESCRIPTION);
    }
    }
    if (element.getAttribute('property') === 'og:url'
    || element.getAttribute('name') === 'twitter:url') {
    element.setAttribute('content', MY_DOMAIN);
    }
    if (element.getAttribute('name') === 'apple-itunes-app') {
    element.remove();
    }
    }
    }

    class HeadRewriter {
    element(element) {
    if (GOOGLE_FONT !== '') {
    element.append(`<link href="https://fonts.googleapis.com/css?family=${GOOGLE_FONT.replace(' ', '+')}:Regular,Bold,Italic&display=swap" rel="stylesheet">
    <style>* { font-family: "${GOOGLE_FONT}" !important; }</style>`, {
    html: true
    });
    }
    element.append(`<style>
    div.notion-topbar > div > div:nth-child(3) { display: none !important; }
    div.notion-topbar > div > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(5) { display: none !important; }
    div.notion-topbar > div > div:nth-child(6) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(3) { display: none !important; }
    div.notion-topbar-mobile > div:nth-child(4) { display: none !important; }
    div.notion-topbar > div > div:nth-child(1n).toggle-mode { display: block !important; }
    div.notion-topbar-mobile > div:nth-child(1n).toggle-mode { display: block !important; }
    </style>`, {
    html: true
    })
    }
    }

    class BodyRewriter {
    constructor(SLUG_TO_PAGE) {
    this.SLUG_TO_PAGE = SLUG_TO_PAGE;
    }
    element(element) {
    element.append(`<div style="display:none">Powered by <a href="http://fruitionsite.com">Fruition</a></div>
    <script>
    window.CONFIG.domainBaseUrl = 'https://${MY_DOMAIN}';
    const SLUG_TO_PAGE = ${JSON.stringify(this.SLUG_TO_PAGE)};
    const PAGE_TO_SLUG = {};
    const slugs = [];
    const pages = [];
    const el = document.createElement('div');
    let redirected = false;
    Object.keys(SLUG_TO_PAGE).forEach(slug => {
    const page = SLUG_TO_PAGE[slug];
    slugs.push(slug);
    pages.push(page);
    PAGE_TO_SLUG[page] = slug;
    });
    function getPage() {
    return location.pathname.slice(-32);
    }
    function getSlug() {
    return location.pathname.slice(1);
    }
    function updateSlug() {
    const slug = PAGE_TO_SLUG[getPage()];
    if (slug != null) {
    history.replaceState(history.state, '', '/' + slug);
    }
    }
    function onDark() {
    el.innerHTML = '<div title="Change to Light Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgb(46, 170, 220); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(12px) translateY(0px);"></div></div></div></div>';
    document.body.classList.add('dark');
    __console.environment.ThemeStore.setState({ mode: 'dark' });
    };
    function onLight() {
    el.innerHTML = '<div title="Change to Dark Mode" style="margin-left: auto; margin-right: 14px; min-width: 0px;"><div role="button" tabindex="0" style="user-select: none; transition: background 120ms ease-in 0s; cursor: pointer; border-radius: 44px;"><div style="display: flex; flex-shrink: 0; height: 14px; width: 26px; border-radius: 44px; padding: 2px; box-sizing: content-box; background: rgba(135, 131, 120, 0.3); transition: background 200ms ease 0s, box-shadow 200ms ease 0s;"><div style="width: 14px; height: 14px; border-radius: 44px; background: white; transition: transform 200ms ease-out 0s, background 200ms ease-out 0s; transform: translateX(0px) translateY(0px);"></div></div></div></div>';
    document.body.classList.remove('dark');
    __console.environment.ThemeStore.setState({ mode: 'light' });
    }
    function toggle() {
    if (document.body.classList.contains('dark')) {
    onLight();
    } else {
    onDark();
    }
    }
    function addDarkModeButton(device) {
    const nav = device === 'web' ? document.querySelector('.notion-topbar').firstChild : document.querySelector('.notion-topbar-mobile');
    el.className = 'toggle-mode';
    el.addEventListener('click', toggle);
    nav.appendChild(el);
    onLight();
    }
    const observer = new MutationObserver(function() {
    if (redirected) return;
    const nav = document.querySelector('.notion-topbar');
    const mobileNav = document.querySelector('.notion-topbar-mobile');
    if (nav && nav.firstChild && nav.firstChild.firstChild
    || mobileNav && mobileNav.firstChild) {
    redirected = true;
    updateSlug();
    addDarkModeButton(nav ? 'web' : 'mobile');
    const onpopstate = window.onpopstate;
    window.onpopstate = function() {
    if (slugs.includes(getSlug())) {
    const page = SLUG_TO_PAGE[getSlug()];
    if (page) {
    history.replaceState(history.state, 'bypass', '/' + page);
    }
    }
    onpopstate.apply(this, [].slice.call(arguments));
    updateSlug();
    };
    }
    });
    observer.observe(document.querySelector('#notion-app'), {
    childList: true,
    subtree: true,
    });
    const replaceState = window.history.replaceState;
    window.history.replaceState = function(state) {
    if (arguments[1] !== 'bypass' && slugs.includes(getSlug())) return;
    return replaceState.apply(window.history, arguments);
    };
    const pushState = window.history.pushState;
    window.history.pushState = function(state) {
    const dest = new URL(location.protocol + location.host + arguments[2]);
    const id = dest.pathname.slice(-32);
    if (pages.includes(id)) {
    arguments[2] = '/' + PAGE_TO_SLUG[id];
    }
    return pushState.apply(window.history, arguments);
    };
    const open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function() {
    arguments[1] = arguments[1].replace('${MY_DOMAIN}', 'www.notion.so');
    return open.apply(this, [].slice.call(arguments));
    };
    </script>${CUSTOM_SCRIPT}`, {
    html: true
    });
    }
    }

    async function appendJavascript(res, SLUG_TO_PAGE) {
    return new HTMLRewriter()
    .on('title', new MetaRewriter())
    .on('meta', new MetaRewriter())
    .on('head', new HeadRewriter())
    .on('body', new BodyRewriter(SLUG_TO_PAGE))
    .transform(res);
    }
  2. 配置对应的路由,也就是访问我们的域名后,需要执行我们定义的notion worker

    Untitled

    路由为我们的域名加上/*

    Worker为刚才新建的worker

    Untitled

    现在你的个人博客就大功告成了

添加Google Analytics脚本监控你的网站

进入Google Analytics后台 ,点击左上侧管理,选择需要添加代码的网站对应的账号和媒体资源后,依次点击媒体资源下的管理-数据流,可以看到如下所示界面:

Untitled

将如下的代码添加到worker中的自定义片段中就可以对你的博客进行访问监控。

Untitled


selenium性能优化

selenium性能优化

当我们使用selenium制作爬虫或自动化测试时,往往会消耗更大的资源以及多余的等待页面资源加载时间。

大部分的优化手段都在options中进行设置

1
2
3
4
5
6
7
8
9
10
11
chrome_options = Options()
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--allow-running-insecure-content')
chrome_options.add_argument("blink-settings=imagesEnabled=false")
driver = webdriver.Chrome(options=chrome_options)

配置headless模型

没有界面的模型,可以节省很多的内存和cpu占用,而且禁用了cpu渲染,提高加载速度。

1
2
3
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")

配置不加载图片

图片通常在网络中加载消耗较多的时间,而且渲染较慢,如果不需要即时加载图片的话可以禁用图片加载

1
chrome_options.add_argument("blink-settings=imagesEnabled=false")

禁用插件加载

1
chrome_options.add_argument("--disable-extensions")

windows发现linux中的samba服务

0X0

通常我们在windows上搭建samba(smb)服务,如果两台机子再同一个内网的话,并且都开机主机发现功能的话,两个主机是可以再网络中发现的,并且可以使用smb服务。但是通常我们linux中搭建的samba服务通常都是使用ip进行访问,如果是静态地址还好,可以获取到固定的ip,但是如果是动态地址的话,还必须使用ddns进行动态ip映射。

对于Windows 10版本1511以后的版本,默认情况下禁用对SMBv1的支持,因此NetBIOS设备发现被禁用。所以再ubuntu中安装的samba服务无法被windows发现。
WSDD 是 Web Service Discovery host daemon的简称,实现了web服务可以被windows主机发现,这可以有效的帮助smb服务被发现。

安装

安装wsdd

ubuntu

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
# cd tmp
cd /tmp

# 下载和解压
wget https://github.com/christgau/wsdd/archive/master.zip
unzip master.zip

# 重命名wsdd.py
sudo mv wsdd-master/src/wsdd.py wsdd-master/src/wsdd

# 复制到bin中
sudo cp wsdd-master/src/wsdd /usr/bin

# 将wsdd配置为服务
sudo cp wsdd-master/etc/systemd/wsdd.service /etc/systemd/system
sudo nano /etc/systemd/system/wsdd.service

#########################################################################
[Unit]
Description=Web Services Dynamic Discovery host daemon
; Start after the network has been configured
After=network-online.target
Wants=network-online.target
; It makes sense to have Samba running when wsdd starts, but is not required
;Wants=smb.service

[Service]
Type=simple
ExecStart=/usr/bin/wsdd --shortlog
; Replace those with an unprivledged user/group that matches your environment,
; like nobody/nogroup or daemon:daemon or a dedicated user for wsdd
; User=nobody
; Group=nobody
; The following lines can be used for a chroot execution of wsdd.
; Also append '--chroot /run/wsdd/chroot' to ExecStart to enable chrooting
;AmbientCapabilities=CAP_SYS_CHROOT
;ExecStartPre=/usr/bin/install -d -o nobody -g nobody -m 0700 /run/wsdd/chroot
;ExecStopPost=rmdir /run/wsdd/chroot

[Install]
WantedBy=multi-user.target

# reload start and enable
sudo systemctl daemon-reload
sudo systemctl start wsdd
sudo systemctl enable wsdd

# 查看状态

sudo service wsdd status

CentOS, Fedora, RHEL

1
dnf install wsdd

引用

https://devanswers.co/discover-ubuntu-machines-samba-shares-windows-10-network/
https://github.com/christgau/wsdd


ubuntu18.04安装g++10

由于想使用c++20中最新的协程特性,这应该是最简单的安装方式,避免了编译的g++。

1
2
3
4
5
# 添加ppa库
sudo add-apt-repository ppa:ubuntu-toolchain-r/test

# 安装g++
sudo apt install g++-10

ubuntu系统调节GPU风扇转速

ubuntu系统调节GPU风扇转速

查看NVIDIA GPU温度和转速

1
nvidia-smi

有桌面的ubuntu

可以通过nvidia-settings进项图像化界面的设置。

无桌面ubuntu[headless linux server]

使用coolgpus脚本进行调节

https://github.com/andyljones/coolgpus
安装:
使用pypi进行安装

1
pip install coolgpus

使用用例:

1
2
3
4
5
6
7
8
9
# 将gpu风扇转速设置为99%
sudo $(which coolgpus) --speed 99 99

# 关闭设置
sudo $(which coolgpus)

# 或者也可以设置线性控制
# 这个模式下20℃以下转速为5%, 20-55℃之间转速为30%,依次类推
sudo $(which coolgpus) --temp 20 55 80 --speed 5 30 99

如果需要将coolgpus脚本当作一个系统服务长期运行的话,如果你的服务器采用systemd管理server的话,可以在/etc/systemd/system/coolgpus.service创建模板

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Headless GPU Fan Control
After=syslog.target

[Service]
ExecStart=/home/ajones/conda/bin/coolgpus --kill
Restart=on-failure
RestartSec=5s
ExecStop=/bin/kill -2 $MAINPID
KillMode=none

[Install]
WantedBy=multi-user.target

可以通过如下命令进行控制

1
2
sudo systemctl enable coolgpus
sudo systemctl start coolgpus

vscode中docker插件无法连接

vscode中docker插件无法连接

报错Failed to connect. Is Docker running
Error: connect EACCES /var/run/docker.sock

原因分析

原因是docker使用unix socket进行通讯,但是unix socket属于root用户,但是普通用户需要使用sudo才能开启root权限,但是普通的操作并没有root权限。

解决方案

  1. 使用root用户登录,但是安全性没有保障
  2. 普通用户增加到docker组中
    1
    2
    3
    sudo groupadd docker          #添加docker用户组
    sudo gpasswd -a $USER docker #将当前用户添加至docker用户组
    newgrp docker #更新docker用户组
    添加后重新登录vscode,用户重新连接后docker插件会正常运行。

测试

可以运行docker ps命令,如果有正常的输出,则正常。


张拉结构小摆饰[stl文件]

张拉整体(Tensegrity)是一种基于在连续张力网络内部应用受压构建的结构原理。其中,受压构件之间并不接触,而预先张拉的构件构成了空间外形

3D渲染图

stl文件下载

最后成品

通过丝线连接后,接头处通过502胶水粘结,虽然有点粗糙但还有点意思🤗


fetch请求中的跨域和携带Cookies问题

fetch解读

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。这种功能以前是使用 XMLHttpRequest 实现的。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如Service Workers。

[问题]fetch请求中的跨域和携带Cookies问题

在有些请求中会遇到希望通过请求来访问不同源的api,并且希望携带cookies。

解决跨域问题

fetch可以设置不同的模式使得请求有效. 模式可在fetch方法的第二个参数对象中定义.

1
fetch(url, {mode: 'cors'});

可以定义的模式如下:

  • same-origin:表示同源可以进行访问,反之浏览器拒绝
  • cors: 表示同源和带有cors响应头的跨域可以请求成功,其他拒绝
  • cors-with-forced-preflight: 表示在发出请求前, 将执行preflight检查.
  • no-cors: 表示跨域请求不带cors响应头场景,此时的相应类型为opaque,但是在opaque的返回类型中,我们几乎不能查看到任何有价值的信息,比如不能查看response, status, url。

解决跨域携带cookies问题

跨域请求中需要带有cookie时, 可在fetch方法的第二个参数对象中添加credentials属性, 并将值设置为”include”.

1
2
3
fetch(url,{
credentials: 'include'
});

除此之外, credentials 还可以取以下值:

  • omit: 缺省值, 默认为该值.
  • same-origin: 同源, 表示同域请求才发送cookie.

但是同时需要目标服务器可以接受接受跨域发送cookies请求,否则会被浏览器的同源策略阻挡
服务器端需要支持Access-Control-Allow-Credentials策略,服务器同时设置Access-Control-Allow-Credentials响应头为"true", 即可允许跨域请求携带 Cookie。


3D打印鼓风机清灰

外壳

叶轮

最后的成品


无聊的考试中干什么都有意思,打印个鼓风机发现还能给键盘清灰还算有点用。


nodejs搭建mqtt服务器(Broker)

MQTT通讯协议

MQTT(消息队列遥测传输)是一种开放式OASIS和ISO标准的轻量级发布订阅网络协议,可在设备之间传输消息,是当下使用广泛的物联网通信协议之一。

IBM公司开发了MQTT协议的第一个版本, 设计思想是轻巧、开放、简单、规范,易于实现。
这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。

图中包括了MQTT协议中的两个角色和基本的订阅和发布模式

  • broker/client

    broker为代理端也就是我们要搭建的服务器,client也就是每个终端温度传感器或者是电脑笔记本等

  • 发布/订阅

    手机或者电脑client向broker订阅一个temp topic,当温度传感器client发布一个temp topic则手机或者电脑同时可以接收到订阅的内容

搭建mqtt Broker

mqtt Broker使用aedes进行搭建

1
2
3
4
5
6
7
const aedes = require('aedes')()
const server = require('net').createServer(aedes.handle)
const port = 18080

server.listen(port, function () {
console.log('server started and listening on port ', port)
})

测试mqtt Broker

搭建两个客户端进行模拟,每隔一秒发送一次temp。客户端使用mqtt.js

  1. client_pub.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var mqtt = require("mqtt")
    var client = mqtt.connect("mqtt://localhost:18080")

    // 连接后不断发布temp topic
    client.on("connect", (e) => {
    console.log("success connect mqtt server");
    setInterval(() => {
    client.publish("temp", "25.6")
    console.log("send it")
    }, 1000)
    })
  2. client_sub.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var mqtt = require("mqtt")
    var client = mqtt.connect("mqtt://localhost:18080")

    // 连接后订阅temp topic
    client.on('connect', (e) => {
    console.log("success connect mqtt server");
    client.subscribe('temp', function (err) {
    console.log("subscribe temp topic")
    })
    })

    // 监听订阅的message
    client.on('message', function (topic, message) {
    // message is Buffer
    console.log(topic + ":\t" + message.toString())
    })

启动服务后运行两个客户端得到以下结果:

  1. 服务器
    1
    server started and listening on port  18080
  2. 客户端
    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
    # node mqtt-pub.js 
    success connect mqtt server
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it
    send it

    # node mqtt-sub.js
    success connect mqtt server
    subscribe temp topic
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6
    temp: 25.6