前言

许久没有折腾博客了,就借着双11买了台国内服务器建站的时机来水一篇,恢复一下节奏。因为很久没用才发现之前用的Travis CI已经不知道因为什么原因触发不了了,就正好也折腾一下,换成了Github Action。

Github Action

鉴于之前根本没有接触过Github Action的写法,所以解决问题的核心就是找几个例子看一下,然后看看怎么改装现有的CI脚本。

copy from

这里借鉴的是如何使用Github+Actions实现Hexo博客自动化部署里的CI yml配置文件。主要的变化也就是Secret的语法稍微有点不同,变成了

1
${{ secrets.TRAVIS_CI }}

而且也不知道为什么之前配置的Secret过期了就重新配置了一个,然后就把之前的shell直接搬运到RUN里,加入之前安装的插件就OK了。

快速搭建服务器

双11薅了TX云三年服务器的羊毛,然后买了个域名,计划着把之前一部分在HK服务器上和树莓派上的服务都整到国内的服务器上,一是访问速度更快,二是比家里的树梅派更稳定一点(老是断电)。由于之前用Docker Compose搭建了一部分的服务感觉很方便,所以就计划这次的服务器上所有的服务都基于Docker和Docker Compose来搭建。

composerize

网页版

一个用来转换docker run命令到Compose文件的工具,如果官方的docker镜像没有给出compose的话就用这个做一下转化,省的自己慢慢写了。

Portainer

因为计划全部用Docker来搭建,所以就选择了一个Docker的Web管理工具,后面的全部应用都基于Portainer的Compose功能来启动和管理了。

启动

直接使用官方的命令:

1
2
3
4
5
6
7
8
9
docker volume create portainer_data
docker run -d \
-p 8000:8000 \
-p 9000:9000 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
cr.portainer.io/portainer/portainer-ce

先启动项目然后开启服务器的9000端口用来临时访问(后面再关掉)。

bunkerized-nginx

这是一个自带TLS证书签发续签的Nginx镜像,正好省了自己去配置续签定时任务的功夫就直接用上了。在Portainer的Stack里直接创建一个Stack:

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
version: '3'
services:
mybunkerized:
image: bunkerity/bunkerized-nginx
ports:
- 80:8080
- 443:8443
volumes:
# - /bilibifun.cn/www:/www:ro
- /bilibifun.cn/certs:/etc/letsencrypt
- /bilibifun.cn/server-confs:/server-confs
- /bilibifun.cn/modsec-crs-confs:/modsec-crs-confs
environment:
- SERVE_FILES=no
- SERVER_NAME=www.bilibifun.cn
- AUTO_LETS_ENCRYPT=yes
- USE_REVERSE_PROXY=yes
# - PROXY_REAL_IP=yes
- DISABLE_DEFAULT_SERVER=yes
- REDIRECT_HTTP_TO_HTTPS=yes
- REVERSE_PROXY_WS=yes
- USE_BAD_BEHAVIOR=no
- USE_LIMIT_REQ=no
- REVERSE_PROXY_KEEPALIVE=yes
- BLOCK_USER_AGENT=no
- ALLOWED_METHODS=GET|POST|HEAD|PROPFIND|DELETE|PUT|MKCOL|MOVE|COPY|PROPPATCH|REPORT
- USE_MODSECURITY=no

关于voluems里的certs的文件夹映射一定一定一定要看一下官方文档,因为有一定的文件权限问题(反正先看看官方Start肯定没有错)。modsec-crs-confs和下面一堆的配置是之前折腾Bitwarden的时候搞的,现在也不知道哪些是一定要配置的,建议先从最简单的配置出发然后逐渐改成能用的就行。USE_MODSECURITY这个配置项是安全相关的,建议在网站调试期间先关掉,不然会触发主动BAN IP的功能。

server-confs

这个就是配置location转发的地方了,因为只签了www的二级域名,所以后面全部的功能的反向代理都用baseUrl的方式来进行。配置的时候就直接往里面丢conf就行

server-confs/portainer.conf

先给portainer配上。不过因为修改server-conf都需要重启nginx,所以可以等最后再用域名形式访问。

1
2
3
4
5
6
7
location /portainer/ {
proxy_pass http://172.17.0.1:9000;
rewrite ^/portainer/(.*) /$1 break;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

v2rayA

建于某些不可知的原因,TTRSS里的有些订阅源需要进行特殊处理,所以在搭建ttrss之前就需要先整个这玩意儿了。

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'
services:
v2raya:
privileged: false
network_mode: host
container_name: v2raya
environment:
- 'V2RAYA_ADDRESS=0.0.0.0:9002'
volumes:
- '/lib/modules:/lib/modules'
- '/etc/resolv.conf:/etc/resolv.conf'
- '/etc/v2raya:/etc/v2raya'
image: mzz2017/v2raya

由于不使用host模式配置过于复杂,这里就直接给整成host模式了,当然最好Docker还是不要使用host模式。。

server-confs/v2raya.conf

1
2
3
4
5
6
7
8
location /v2raya/ {
proxy_pass http://172.17.0.1:9002/;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

只代理了web页面,因为暴露的socks5端口只在本地使用,就没有关系了。

TTRSS

当然这玩意儿才是最核心的了,但是在Awesome-TTRSS这个项目的加持之下,ttrss的安装已经变得非常简单了,直接拿文档里的Compose文件来改改就行。

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
version: "3"
services:
service.rss:
image: wangqiru/ttrss:latest
container_name: ttrss
ports:
- 9001:80
environment:
- SELF_URL_PATH=https://www.bilibifun.cn #这里虽然是baseUrl但还是只要填域名就好
- DB_PASS=AAAAAAAAAA # use the same password defined in `database.postgres`
- PUID=1000
- PGID=1000
- SESSION_COOKIE_LIFETIME=720 # 增加session时长
- HTTP_PROXY=172.17.0.1:20171 # 这里就是神秘软件的端口了
volumes:
- feed-icons:/var/www/feed-icons/
networks:
- public_access
- service_only
- database_only
stdin_open: true
tty: true

service.mercury: # set Mercury Parser API endpoint to `service.mercury:3000` on TTRSS plugin setting page
image: wangqiru/mercury-parser-api:latest
container_name: mercury
networks:
- public_access
- service_only

service.opencc: # set OpenCC API endpoint to `service.opencc:3000` on TTRSS plugin setting page
image: wangqiru/opencc-api-server:latest
container_name: opencc
environment:
- NODE_ENV=production
networks:
- service_only

database.postgres:
image: postgres:13-alpine
container_name: postgres
environment:
- POSTGRES_PASSWORD=AAAAAAAAAA # feel free to change the password
volumes:
- ttrss_postgres:/var/lib/postgresql/data # persist postgres data to ~/postgres/data/ on the host
networks:
- database_only

volumes:
feed-icons:
ttrss_postgres:

networks:
public_access: # Provide the access for ttrss UI
service_only: # Provide the communication network between services only
internal: true
database_only: # Provide the communication between ttrss and database only
internal: true

server-confs/ttrss.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location /ttrss/ {
rewrite /ttrss/(.*) /$1 break;
proxy_redirect https://$http_host https://$http_host/ttrss;
proxy_pass http://172.17.0.1:9001;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN;

client_max_body_size 100m;
client_body_buffer_size 128k;

proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}

这个就直接按照官网的配置来就行,只是网页上有些功能还是不能正常使用,比如点标题的链接会404 = =。

RSSHUB

这个也很简单,官方文档有Compose文件,直接用

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
version: '3'

services:
rsshub:
image: diygod/rsshub
ports:
- '9003:1200'
environment:
NODE_ENV: production
CACHE_TYPE: redis
REDIS_URL: 'redis://redis:6379/'
PUPPETEER_WS_ENDPOINT: 'ws://browserless:3000'
depends_on:
- redis
- browserless

browserless:
# See issue 6680
image: browserless/chrome:1.43-chrome-stable
ulimits:
core:
hard: 0
soft: 0

redis:
image: redis:alpine
volumes:
- redis-data:/data

volumes:
redis-data:

rsshub.conf

1
2
3
4
5
6
7
8
location /rsshub/ {
proxy_pass http://172.17.0.1:9003/;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

Bitwarden

这个也是官方有Compose,拿来直接用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: "3"

services:
bitwarden:
image: vaultwarden/server
container_name: bitwarden-server
ports:
- "9004:80"
- "9005:3012"
volumes:
- bitwarden:/data
environment:
WEBSOCKET_ENABLE: "true"
SIGNUPS_ALLOWED: "true"
WEB_VAULT_ENABLE: "true"
DOMAIN: "https://bilibifun.cn/bitwarden"
ADMIN_TOKEN: "AAAAAAAAAAA"
volumes:
bitwarden:

bitwarden.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
location /bitwarden/ {
proxy_pass http://172.17.0.1:9004;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location /bitwarden/notifications/hub {
proxy_pass http://172.17.0.1:9005;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location /bitwarden/notifications/hub/negotiate {
proxy_pass http://172.17.0.1:9004;
}

这个似乎也是官方给出的配置,直接拿来用就行。

NPS

用来穿透一下树莓派,挂一下WEBDAV用的。

1
2
3
4
5
6
7
8
9
10
11
version: '3.3'
services:
nps:
container_name: nps
ports:
- '9009:8024'
- '9010:8080'
- '10000-10020:10000-10020'
volumes:
- '/root/conf:/conf'
image: ffdfgdfg/nps

nps.conf

1
2
3
4
5
6
location /nps/ {
proxy_pass http://172.17.0.1:9010;
proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

VuePress

因为网站还是缺个首页,所以就打算用vuepress整一个试试水,本来是打算给博客直接从hexo搬到vuepress的,但是现在vuepress-next还没有很多主题跟进,就还是先用官方默认的搭一下首页就行。大致看了下官方文档,然后找了篇1小时搞定vuepress快速制作vue文档/博客+免费部署预览直接拷贝一下,因为现在还没有内容,只是搞个首页然后给其他工具做个导航,就只做了一下config.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = {
lang: 'zh-CN',
title: '批哩FUN',
description: '批哩FUN',
head: [['link', { rel: 'icon', href: 'logo.jpg' }]],

themeConfig: {
logo: 'logo.jpg',

//顶部导航栏
navbar: [
{text:'我的博客', link: 'https://misakatang.cn'},
{text:'TTRSS', link: 'https://www.bilibifun.cn/ttrss'},
{text:'Portainer', link: 'https://www.bilibifun.cn/portainer'},
{text:'青龙面板', link: 'http://110.40.154.33:9006'},
{text:'NPS', link: 'https://www.bilibifun.cn/nps'},
{text:'Bitwarden', link: 'https://www.bilibifun.cn/bitwarden'},
{text:'RSS Hub', link: 'https://www.bilibifun.cn/rsshub'},
{text:'V2rayA', link: 'https://www.bilibifun.cn/v2raya'},
{text:'WEBDAV', link: 'https://www.bilibifun.cn/webdav'},
],
},
}

效果:批哩FUN

部署

因为暂时没有什么功能,而且Github Action操作服务器还没研究过,暂时就先把项目clone到服务器上然后写个定时任务每天跑一次构建到docker就行了

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM node:17-alpine as builder

COPY . /workdir
WORKDIR /workdir
RUN ls
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
RUN cnpm -v
RUN cnpm i yarn tyarn -g
RUN tyarn -v
RUN tyarn
RUN tyarn docs:build

# 选择更小体积的基础镜像
FROM nginx:alpine
COPY --from=builder /workdir/docs/.vuepress/dist /usr/share/nginx/html

build.sh

1
2
3
4
5
6
7
8
9
git pull

docker build -t bilibifun-home .

(docker stop bilibifun-home && docker rm bilibifun-home) || true

docker run -p 9013:80 --name bilibifun-home -d bilibifun-home

echo y | docker system prune

home.conf

1
2
3
4
5
6
7
8
location / {
proxy_pass http://172.17.0.1:9013/;
proxy_set_header Host $host;
#proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

后续升级

  • 现在的server-conf还需要在服务器上通过shell来更改,如果有必要的话可以整一个运维工具或者装一个VSCode web版来进行在线的编辑
  • 官网首页的内容规划,如果有更多的内容的话就找一个更好的CI方式。
  • Jellyfin + Aria : 因为树梅派还跑着Aria下电影和番剧,可以考虑webdav之外的播放形式
  • 挂载阿里网盘为webdav用来存电影
  • 把aria穿透出来并且搞成配置式的url

前言

看了一下在阿里云买的hk服务器每个月得34,算下来一年也不少钱了

而且内存也开始不是很够用了就打算把服务都迁移到树莓派上(主要是ttrss),然后就折腾了将近一个星期都差不多迁移完成了.

唯一的深坑就是某些docker镜像不支持arm架构没法继续使用了吧(比如Huginn).

安装Ubuntu Server

官方文档:How to install Ubuntu on your Raspberry Pi

镜像源

/etc/apt/sources.list

1
2
3
4
5
6
7
8
9
10
11
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ b#  清华
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse

NPS

文档

安装直接下载二进制文件执行

服务端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 因为服务器上配置了nginx所以取消监听80和443端口
#HTTP(S) proxy port, no startup if empty
http_proxy_ip=0.0.0.0
#http_proxy_port=80
#https_proxy_port=443
#https_just_proxy=true
#default https certificate setting
#https_default_cert_file=conf/server.pem
#https_default_key_file=conf/server.key

## 指定服务端连接时的端口
##bridge
bridge_type=tcp
bridge_port=444
bridge_ip=0.0.0.0

## 管理面板配置
#web
web_host= 1.1.1.1
web_username=admin
web_password=admin
web_port = 8080

客户端

1
2
3
./npc -server=1.1.1.1:444 -vkey=key
./npc install -server=1.1.1.1:444 -vkey=key
sudo npc start

v2ray安装

在没有代理的情况下哪怕配置了docker的国内镜像源,下载v2ray的镜像还是非常的折磨,所以选择直接从官网下载了二进制包直接使用.

启动脚本

偷了服务器上原来的service配置

/etc/systemd/system/v2ray.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=V2Ray Service
After=network.target
Wants=network.target

[Service]
# This service runs as root. You may consider to run it as another user for security concerns.
# By uncommenting the following two lines, this service will run as user v2ray/v2ray.
# More discussion at https://github.com/v2ray/v2ray-core/issues/1011
# User=v2ray
# Group=v2ray
Type=simple
PIDFile=/run/v2ray.pid
ExecStart=/usr/bin/v2ray/v2ray -config /etc/v2ray/config.json
Restart=on-failure
# Don't restart in the case of configuration error
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

客户端配置

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
{
"log": {
"access": "/var/log/v2ray/access.log",
"error": "/var/log/v2ray/error.log",
"loglevel": "warning"
},
"inbounds": [
{
"port":"7892",
"protocol":"http",
"settings":{},
"tag":"in-1"
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "",
"port": 443,
"users": [
{
"id": "",
"alterId": 64,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"allowInsecure": true,
"serverName": ""
},
"tcpSettings": null,
"kcpSettings": null,
"wsSettings": {
"connectionReuse": true,
"path": "",
"headers": {
"Host": ""
}
},
"httpSettings": null,
"quicSettings": null
},
"mux": {
"enabled": true
}
}
]
}

因为大部分的软件配置代理都无法通过socks,所以直接配置了http的代理使用

docker安装

一键脚本

1
2
curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh --mirror Aliyun

配置docker的代理(镜像源感觉并没有什么卵用, pull v2ray的时候还是跟爬一样)

1
2
3
4
5
6
7
8
9
10
11
12
sudo mkdir -p /etc/systemd/system/docker.service.d
## 文件写入下面配置
/etc/systemd/system/docker.service.d/http-proxy.conf
/etc/systemd/system/docker.service.d/https-proxy.conf
## 配置 v2ray配置的http代理
[Service]
Environment="HTTPS_PROXY=http://127.0.0.1:1080/"

sudo systemctl daemon-reload
sudo systemctl restart docker
## 查看配置
systemctl show --property=Environment docker

TTRSS

使用了Awesome TTRSS

通过 docker-compose 部署

主要增加了代理配置,直接配置了全局的http代理(毕竟有些订阅源)

1
2
environment:
- HTTP_PROXY=http://172.17.0.1:7892

这个ip是主机上的docker0的虚拟网卡地址:(总比那些动不动就叫人-h的好多了)

IPv4 address for docker0: 172.17.0.1

挂载硬盘

查看设备:

sudo lsblk -o UUID,NAME,FSTYPE,SIZE,MOUNTPOINT,LABEL,MODEL

安装驱动:

sudo apt-get install ntfs-3g

手动mount:

sudo mount /dev/sdc2 /mnt/share

并没有配置开机自动挂载,因为连接硬盘启动一次失败之后就懒得折腾了,毕竟一直开着启动次数也不多,唯一的坑是如果把某些docker镜像设置了always启动而服务器启动之后硬盘没有正确挂载的话-v的文件夹内外会不一致,暂时的解决办法是把always启动关闭手动启动docker镜像在挂载硬盘之后

Aria2 Pro

1
2
3
4
5
6
7
8
9
10
11
12
13
docker run -d \
--name aria2-pro \
--restart unless-stopped \
--log-opt max-size=1m \
--network host \
-e PUID=$UID \
-e PGID=$GID \
-e RPC_SECRET=<TOKEN> \
-e RPC_PORT=6800 \
-e LISTEN_PORT=6888 \
-v ~/aria2-config:/config \
-v ~/aria2-downloads:/downloads \
p3terx/aria2-pro
1
2
3
4
5
6
docker run -d \
--name ariang \
--restart unless-stopped \
--log-opt max-size=1m \
-p 6880:6880 \
p3terx/ariang

因为下载文件配置在了挂载的机械盘上面,所以启动之后需要在面板的高级设置里把文件分配方法改为prealloc,否则下载失败

Git ssh代理配置

git clone时使用ssh方式clone需要在ssh中配置:

~/.ssh/config

1
2
3
4
5
6
7
8
# 必须是 github.com
Host github.com
HostName github.com
User git
# 走 HTTP 代理
ProxyCommand connect -H 127.0.0.1:7892 %h %p
# 走 socks5 代理(如 Shadowsocks)
# ProxyCommand nc -v -x 127.0.0.1:1080 %h %p

ajenti

1
sudo pip3 install --proxy http://127.0.0.1:7892 --cache-dir=/home/ubuntu/piptmp --build /home/ubuntu/piptmp ajenti-panel ajenti.plugin.ace ajenti.plugin.augeas ajenti.plugin.auth-users ajenti.plugin.core ajenti.plugin.dashboard ajenti.plugin.datetime ajenti.plugin.filemanager ajenti.plugin.filesystem ajenti.plugin.network ajenti.plugin.notepad ajenti.plugin.packages ajenti.plugin.passwd ajenti.plugin.plugins ajenti.plugin.power ajenti.plugin.services ajenti.plugin.settings ajenti.plugin.terminal

–cache-dir=/home/ubuntu/piptmp –build /home/ubuntu/piptmp

用来解决/var/tmp空间不足的问题,自行决定路径

磁盘空间管理

journalctl 命令自动维护文件大小

journalctl --vacuum-size=500M

docker磁盘分析

1
2
3
docker system df
## 清理
docker system prune

df du使用

1
2
df -h 
du -h -d 1 | grep G

ncdu

sudo apt install ncdu

深入浅出现代Web编程:https://fullstackopen.com/zh/

full_stack_open.xmind

前端

JavaScript

Variables

  • const
  • let
  • var (不建议使用)

Arrays

  • const t2 = t.concat(5)
  • const m1 = t.map(value => value * 2)
  • 解构赋值: const [first, second, ...rest] = t
  • 展开语法
    • […numbers, 4, 5]

Objects

1
2
3
4
5
6
7
8
const object3 = {
name: {
first: 'Dan',
last: 'Abramov',
},
grades: [2, 3, 5, 3],
department: 'Stanford University',
}
  • 对象展开
    • const changedNote = { ...note, important: !note.important }

Functions

  • 箭头函数
    • 完整过程
      1
      2
      3
      4
      5
      const sum = (p1, p2) => {
      console.log(p1)
      console.log(p2)
      return p1 + p2
      }
    • 只有一个参数
      1
      2
      3
      4
      const square = p => {
      console.log(p)
      return p * p
      }
    • 只包含一个表达式
      • const square = p => p * p
  • Object methods and “this”
    • 使用箭头函数可以解决与 this相关的一系列问题

Classes

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
console.log('hello, my name is ' + this.name)
}
}
const adam = new Person('Adam Ondra', 35)
adam.greet()

Destructuring (解构)

1
2
3
4
5
6
7
8
9
10
const Hello = ({ name, age }) => {  const bornYear = () => new Date().getFullYear() - age
return (
<div>
<p>
Hello {name}, you are {age} years old
</p>
<p>So you were probably born in {bornYear()}</p>
</div>
)
}
  • 解构使变量的赋值变得更加容易,因为我们可以使用它来提取和收集对象属性的值,将其提取到单独的变量中
  • const { name, age } = props
  • const Hello = ({ name, age }) => {

对象的展开语法

1
2
3
4
5
6
7
const handleLeftClick = () => {
const newClicks = {
...clicks,
left: clicks.left + 1
}
setClicks(newClicks)
}
  • { ...clicks, right: 1 }
  • Math.max(...notes.map(n => n.id))

promises(axios)

  • 表示异步操作的对象
  • 注册一个事件处理访问操作的结果 ==> .then()
    1
    2
    3
    4
    5
    6
    axios
    .get('http://localhost:3001/notes')
    .then(response => {
    const notes = response.data
    console.log(notes)
    })
  • 错误处理
    1
    2
    3
    .catch(error => {
    console.log('fail')
    })

template string

  • console.log(importance of ${id} needs to be toggled)

Property definitions

  • export default { getAll, create, update }

ESLint

async/await

  • 错误处理
    • express-async-errors

local storage

  • window.localStorage

TypeScript

  • 主要语言特性
    • 类型注解
      • const birthdayGreeter = (name: string, age: number): string => {
    • 结构化类型
    • 类型推断
    • 类型擦除
  • ts-node-dev
  • 工具类型
    • Pick
    • Omit

React

Component 组件

  • 语法
    • 箭头函数 + 代码简写

      1
      2
      3
      4
      5
      const App = () => (
      <div>
      <p>Hello world</p>
      </div>
      )
    • 不简写版本

      1
      2
      3
      4
      5
      6
      7
      const App = () => {
      return (
      <div>
      <p>Hello world</p>
      </div>
      )
      }
    • 多组件

      • React 的核心理念,就是将许多定制化的、可重用的组件组合成应用
    • props

      • 向组件传递数据
        • const Hello = (props) =>
        • <Hello name="Maya" age={26 + 10} />
      • 将状态传递给子组件
        • 状态提升
          • 将共享状态提升到它们最接近的共同祖先
    • 组件辅助函数: 在函数中定义函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const Hello = (props) => {
      const bornYear = () => { const yearNow = new Date().getFullYear() return yearNow - props.age }
      return (
      <div>
      <p>
      Hello {props.name}, you are {props.age} years old
      </p>
      <p>So you were probably born in {bornYear()}</p> </div>
      )
      }
    • 将事件处理传递给子组件

      • handleClick = { () => func() }
    • 受控组件

      • 组件现在控制了元素的行为
      • 注册一个事件处理来同步对元素所做的更改和组件的状态
        1
        2
        3
        4
        5
        6
        7
        8
        <input
        value={newNote}
        onChange={handleNoteChange} />

        const handleNoteChange = (event) => {
        console.log(event.target.value)
        setNewNote(event.target.value)
        }
    • 表单

      1
      2
      3
      4
      5
      6
      7
      8
      <form onSubmit={addNote}>
      <input
      value={newNote}
      onChange={handleNoteChange} />
      <button type="submit">save</button>
      </form>

      const handleNoteChange = (event) => { console.log(event.target.value) setNewNote(event.target.value) }

JSX

Hook

  • Hook的规则

    • 不能从循环、条件表达式或任何不是定义组件的函数的地方调用 useState
  • state hook

    • 导入 useState 函数
      • import React, { useState } from 'react'
    • 定义组件的函数体
      • const [ counter, setCounter ] = useState(0)
    • 每次 setCounter 修改状态时,它都会导致组件重新渲染
  • effect hooks

    • 从服务器获取数据时使用的正确工具
      1
      2
      3
      4
      5
      6
      7
      useEffect(() => {
      axios
      .get('http://localhost:3001/notes')
      .then(response => {
      setNotes(response.data)
      })
      }, [])
    • 在函数组件中执行副作用
    • 数据获取、设置订阅和手动更改 React 组件中的 DOM

事件处理

  • 鼠标事件
    • <button onClick={() => console.log('clicked')}>

渲染

  • 页面重渲染

    • ReactDOM.render
  • 条件渲染

  • 渲染集合

    • notes.map(note => <li>{note.content}</li>)
    • Key-属性
      • React 使用数组中对象的key属性来确定组件在重新渲染时,如何更新组件生成的视图
      • <li key={note.id}>

调试

  • React developer tools扩展

React router

  • Parameterized route
  • useHistory

样式

production build (生产构建)

  • npm run build
    • 创建一个名为build 的目录
    • 代码的Minified版本将生成到static 目录
    • 所有的 JavaScript 都将被缩小到一个文件中

Flux-架构

  • redux
    • Action
    • reducer
      • Reducer 状态必须由不可变 immutable 对象组成
    • store
    • dispatch
    • subscribe
    • immutable
  • react-redux
    • Provider
  • deep-freeze
    • 确保 reducer 被正确定义为不可变函数
  • Pure functions
  • redux-thunk

Webpack

后端

Express

  • 路由参数

    • app.get('/api/notes/:id', ...)
      1
      2
      3
      app.get('/api/notes/:id', (request, response) => {
      const id = request.params.id
      }
  • 错误处理

    • response.status(404).end()
  • json-parser

    • app.use(express.json())
  • Promise chaining

REST

  • 统一接口 uniform interface

CORS

  • const cors = require('cors')

部署静态文件

  • app.use(express.static('build'))

MongoDB

错误处理

  • .catch(error => {
  • 移入中间件
  • next 函数
    • .catch(error => next(error))

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── index.js
├── app.js
├── build
│ └── ...
├── controllers
│ └── notes.js
├── models
│ └── note.js
├── package-lock.json
├── package.json
├── utils
│ ├── config.js
│ ├── logger.js
│ └── middleware.js

密钥认证

GraphQL

  • Apollo server

Testing

  • Jest
    • afterAll
    • beforeEach
  • mock
    • mongo-mock
  • supertest

其余

模块提取 (单一职责原则)

  • components
  • service

教材

Node.js

  • 基于谷歌的 chrome V8 引擎的 JavaScript 运行时环境

资源

工具

  • create-react-app
  • JSONView
  • npm
    • json-server
    • axios
    • express
    • nodemon (代码热更新)
      • npm install --save-dev nodemon
    • npm install cors --save
    • Mongoose
      • npm install mongoose --save
    • dotenv
      • npm install dotenv --save
    • npm install eslint --save-dev
    • npm install --save-dev jest
    • npm install --save-dev deep-freeze
    • npm install --save react-redux
    • npm install --save-dev redux-devtools-extension
    • npm install --save-dev ts-node-dev
  • Postman
  • Visual Studio Code REST client
  • Redux DevTools
0%