0%

前言

背景

17年在大学用Hexo部署了第一个博客后,一直对Hexo个人博客进行改造升级。最开始是挂载在Github.io,后用Nginx迁移至自有域名chanchun.net,在后来启用了GitHooks让Hexo构建产物直接SSH推送至云,再后来通过letsencrypt 升级了Https。自从Oracle Cloud开放了免费实例后,一直想把博客服务迁移到免费的云上(无奈工作太忙,也怪搬瓦工服务器太难用)。那么与其从0开始迁移,还不如把一直以来的沉淀(Https、GitHooks、Nginx)也一起Docker化掉。方便以后继续迁移,也给自己想折腾博客的人一个我认为最终极的解决方案。

你需要准备什么

云基础配置

  • 买一个Niubility的域名(推荐国外域名注册商)
  • 买一个Niubility云服务实例(推荐国外云服务商)
  • 把你的域名DNS解析至你的云公网IP(生效时间预计从0-48小时不等)

Hexo游刃有余

  • 熟悉Hexo的基本操作,熟悉hexo init hexo new hexo clean hexo generate hexo server hexo deploy 等命令,并能启动Hexo服务构建出静态html,如果已经搭建过了简单的博客系统就更棒棒

Docker

  • 了解一下Docker基本操作docker build docker run 等等一系列操作

正题

思路

第一步,新建一个hexo-docker工程来构建Docker镜像(基于Nginx镜像),里面有且仅包含Dockerfile和初始Nginx配置。Dockerfile中描述了Githooks挂载地址,安装好了Certbot命令,并加入初始Nginx配置。

第二步,在云服务中直接Docker Run起构造好的镜像,进入镜像生成好letsencrypt证书,配置好远程SSH提供给Hexo Deploy。

第三步,配置好本地的Hexo代码,Hexo Deploy就能看到带绿锁的自有域名的博客了。

Show Code

Dockerfile

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
#基于Nginx镜像
FROM nginx:latest

#换国内的源,这里用网易源
RUN sed -i 's#http://deb.debian.org#https://mirrors.163.com#g' /etc/apt/sources.list
RUN sed -i 's#http://security.debian.org#https://mirrors.163.com#g' /etc/apt/sources.list

#更新Docker中的apt-get
RUN rm -Rf /var/lib/apt/lists/* && apt-get update

#安装Git,挂载Githooks钩子,并将产物放置在var/www/hexo中
RUN apt-get install -y git
RUN git init --bare ~/blogs.git
RUN mkdir -p /var/www/hexo
RUN echo "git --work-tree=/var/www/hexo --git-dir=/root/blogs.git checkout -f" >~/blogs.git/hooks/post-receive
RUN chmod a+x ~/blogs.git/hooks/post-receive

#SSH初始化
RUN apt-get install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -y
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -y
RUN ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -y

#安装Certbot
RUN apt-get install -y certbot
RUN apt-get install -y python3-certbot-nginx

#初始化Nginx配置挂载,这里选择直接覆盖所有配置
ADD nginx/hexo.conf /etc/nginx/nginx.conf

#暴露22 SSH
#暴露80 HTTP
#暴露443 HTTPS
EXPOSE 22
EXPOSE 80
EXPOSE 443

Nginx配置

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
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

#include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name chanchun.net; #记得改成自己的域名

location / {
root /var/www/hexo; #要和刚刚的Githooks目录对应上
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

我已经将代码上传到了Github,给个星星?

构建和启动

构建

1
docker build -t hexo-docker .

启动

这里要在云上Run了,如果在本机跑的化能跑起来但是没法继续搞证书了。至于怎么把构建好的镜像转移到云上呢?各位搞开源的Docker Hub也行,直接Copy也行。

注意这里使用了volume方案,为后续申请证书的挂载做准备

1
docker run -v $(pwd)/letsencrypt:/etc/letsencrypt -d -p 80:80 -p 443:443 -p 8004:22 hexo-docker

我已经将构建出的Docker镜像上传到了Docker Hub,给个星星?

配置证书

进入Docker Container

1
docker exec -it "[your_container_name]" /bin/bash

申请证书

我们在用Dockfile来构建的时候已经安装了Certbot,我们可以利用Certbot帮忙安装Https证书,同时Certbot会自动识别上文中配置的Nginx文件,加入证书验证和443端口开放。

1
certbot --nginx -d "[your_domain.com]"

这里就按照提示一步一步走就是了,最后见到congratulation!就能证明证书已经安装好了,因为我们在docker run 时采用了volume方案,生成出的证书会挂载到云服务的$(pwd)/letsencrypt目录下,并关联到Docker内部的/etc/letsencrypt目录 。这样能保证镜像中不带CA证书,解耦的同时也确保Docker在迁移的时候的安全性。

可以看到/etc/nginx/nginx.conf 已经加入了443端口和证书路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
server_name chanchun.net;

location / {
root /var/www/hexo;
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/chanchun.net/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/chanchun.net/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

证书更新

1
/usr/bin/certbot renew

Letsencrypt 3个月就需要更新一次,很多人用自动更新任务来完成更新。我这边就不想用了,因为我觉得每个月收到提示邮件后上云更新一把是一件很愉快的事。如果你需要自动更新可以使用下面命令

1
echo "0 0 1 * * /usr/bin/certbot renew --quiet" >> /etc/crontabs/root

配置Hexo Deploy的SSH

还是在Docker镜像内操作,不需要离开Docker镜像内部。如果离开了看上述“进入Docker Container”命令。

依次执行下面的命令吧,注意your_ssh_public_key改成自己电脑上的公钥,在~/.ssh/xxx.pub 中。

1
mkdir ~/.ssh
1
echo "[your_ssh_public_key]" > ~/.ssh/authorized_keys
1
chmod 600 ~/.ssh/authorized_keys
1
chmod 700 ~/.ssh
1
mkdir /run/sshd
1
/usr/sbin/sshd

Hexo推送

恭喜你胜利在即,到这一步已经可以退出Docker镜像,并且退出云服务器了(有没有发现完全没改云服务器本身的配置,感叹Docker实在强大!)。接下来都是直接在本机操作。

更改Hexo _config.yml 配置

1
2
3
4
5
deploy:
type: git
repo: root@hexo-test:/root/blogs.git #Docker中8004端口作为SSH端口,但repo没法写端口号,写一个hexo-test作为config
branch: master

新增~/.ssh/config文件

~/.ssh/config中配置你的云服务公网ip,ssh端口即可(按照文章来就是8004)

1
2
3
Host hexo-test
HostName [your_remote_server_ip]
Port 8004

推送Hexo

1
hexo deploy

Success! 当hexo deploy 成功之后,你的博客就可以通过域名访问了,并且带有小绿锁哦。

chanchun.net

总结一下

这套方案有三解耦:云服务和Docker解耦,证书(在云服务上)和Docker解耦,HTML静态资源和Docker解耦(Githooks)。同时Nginx也无需手工配置。这套方案不仅可用于博客,只要是静态文件的部署,都能适用。

最后求个赞吧:GithubDocker Hub

使用Proxychains给你的Shell加上代理

Proxychains是啥?

他是一个命令行预加载器,用于挂钩动态链接程序中的套接字,并通过一个或多个socks / http代理重定向它。

GitHub地址 链接

MacOS和Linux系本身通过Shadowsocks代理之后,通过PAC名单可以识别出要代理的域名。但要求代理应用必须支持Sock5,如果不支持,那么也没有办法通过Shadowsocks代理。我们的Shell控制台环境是不支持Sock代理的,只支持HTTP代理。如果我们不在终端设置 http_proxy 代理,那么我们的Shell控制台是无法通过Shadowsocks代理的。

附 终端设置 http_proxy 代理方法

其实如果不嫌麻烦,我们可以通过在终端设置http_proxy来将Sock代理换成HTTP代理。这样Shell环境也可以曲线救国而使用Shadowsocks代理啦。

1
2
export http_proxy='http://localhost:8888'
export https_proxy='http://localhost:8888'

取消代理

1
2
unset http_proxy
unset https_proxy

如果有兴趣也可以将这个命令放在.bashrc或者.zshrc中,让我们的Shell控制台全局代理启动更加方便一些。

首先你的有自己的小飞机(Mac)

通过Proxychains代理的首先你的有自己的Shadowsocks。这里推荐搭建Shadowsocks-R环境(SSR),因为SSR比普通的Shadowsocks增加了协议和混淆,稳定性更高。

ShadowsocksX-NG 下载这个ShadowsocksX-NG,输入你的SSR配置即可。首先保证你的浏览器能够被ShadowsocksX-NG代理,因为浏览器是默认支持Sock协议的。

如果你是Linux系,你可以借助sslocal命令开启自己的Shadowsocks,具体的参数就不在此文中详细介绍

使用Proxychains4

安装(Mac)

1
brew install proxychains-ng

安装完成之后你将有proxychains4命令

安装(Linux)

1
2
3
4
git clone https://github.com/rofl0r/proxychains-ng.git
cd proxychains-ng
./configure
make && make install

配置参数

修改/usr/local/etc/proxychains.conf 中的配置(Mac brew安装)

注意:Linux系不会默认生成conf文件,需要将上工程中./src/proxychains.conf 里面的配置Copy到目录中

如果proxychain4命令不手动制定配置文件路径,它会自动寻找配置。

1
2
3
4
5
6
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
# socks4 127.0.0.1 9050
socks5 127.0.0.1 1086 # 这里设置自己的代理地址和端口

这就可以了么?

proxychains4配置生效之后,我们在终端使用proxychains4 加上命令就可以在终端代理了。但是这真的可以代理了么???答案是MacOS不行,而Linux系的同学已经OK了。

测试测试:

1
proxychains4 curl ip.cn

结果:

1
当前 IP:42.120.102.128 来自:浙江省杭州市 阿里云

修改苹果SIP(System Integrity Protection)设置

如果是Linux系的 此时你应该成功代理了 忽略下文

macOS 10.11后,苹果加入SIP设置就会导致部分命令是执行不成功的,就包括使用socks的proxychains4。如果我们要解决这个问题,我们就要关掉SIP。

  • 重启Mac,按下Command+R直到出现Apple Logo。
  • 选择实用工具->终端
  • 输入命令csrutil disable。出现类似于“成功禁用SIP”的意思就表示成功了。

如果还要打开SIP,最后的步骤csrutil enable即可

测试关闭SIP之后的Proxychains4

测试测试:

1
proxychains4 curl ip.cn

结果:

1
当前 IP:42.3.27.177 来自:香港特别行政区 香港电讯

如果看到的ip是境外的ip,这样即可成功在命令行中通过proxychains4代理啦。此时如果关掉proxychains4。

测试测试:

1
curl ip.cn

结果:

1
当前 IP:42.120.102.128 来自:浙江省杭州市 阿里云

升级你的hexo为https

本文以Debian 8为服务器栗子

最近升级了个人博客为https协议,写一个详细的教程帮助更多人升级https。

https的详细原理在此文中省略,简略来说,既是客户端在访问服务器时需要一个数字证书(里面包括公钥),它由权威机构(CA, Certificate Authority)颁发,来确认公钥确实是服务器发出来的。

证书价格不菲,大概7天需要几美金到几百美金不等(根据认证的等级和域名覆盖范围区分),所以很多证书都是商业级的,提供给商业网站使用。

获取免费证书

那么个人将无法获取数字证书么?不是的,为了鼓励https的普及,EFF成立了免费证书最大的提供商为Let’s Encrypt,可以提供免费证书。那么小型的网站,就可以使用免费证书升级为https啦。

当然Let’s Encrypt生成的证书,只能是单域名的,而且只有最低级的域名验证。

克隆letsencrypt客户端

1
$ git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

如果遇到权限问题,记得先创建/opt/letsencrypt文件夹再,更改文件夹权限为可写入。

注册证书

Nginx指向静态路径注册证书

注册一个域名证书非常简单,使用letsencrypt就能生成https所需的证书。当然,用letsencrypt生成的证书只支持域名验证,只需要用letsenctypt的自动注册证书命令,证明这个域名是自己的是用的即可。

1
2
$ cd /opt/letsencrypt
$ ./letsencrypt-auto certonly -a webroot --webroot-path=/var/www/me -d me.chanchun.net # 可以使用多个 -d 添加多个域名

letsencrypt可以帮我们自动注册证书,--webroot-path是静态资源所指的路径。-d是域名域名,也可以多个-d增加多个域名。最后确保使用https的域名都被letsencrypt注册。

后续将会让你继续输入邮箱信息

如果出现Congratulations!字样,则证明证书已被自动注册。

Nginx转发型注册证书

如果是使用Nginx直接重定向服务器本地服务,非静态资源,就省略--webroot-path-a参数。

1
2
$ cd /opt/letsencrypt
$ ./letsencrypt-auto certonly -d ca.chanchun.net

输入命令之后

1
2
3
4
5
6
7
How would you like to authenticate with the ACME CA?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Nginx Web Server plugin (nginx)
2: Spin up a temporary webserver (standalone)
3: Place files in webroot directory (webroot)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-3] then [enter] (press 'c' to cancel):

选择1即可,后续将会让你继续输入邮箱信息

如果出现Congratulations!字样,则证明证书已被自动注册。

Nginx配置

直到这一步,证书已经就绪,只要配置好Nginx即可完美升级https。Nginx安装、启动教程请具体请教Nginx教程。

转发到本地服务Nginx配置Sample:

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
#http重定向到https配置 证书验证配置
server {
listen 80;
server_name ca.chanchun.net; # 这里写你的域名
location ^~ /.well-known/acme-challenge/ { # 不要修改 letsencrypt需要验证你的域名
default_type "text/plain";
proxy_pass http://localhost:3000; # 填写你需要转发的服务器地址
}
location = /.well-known/acme-challenge/ { # 不要修改 letsencrypt需要验证你的域名
return 404;
}
return 301 https://$server_name$request_uri;
}

# https配置
server {
# SSL Configuration
listen 443 ssl;
server_name ca.chanchun.net; # 这里写你的域名

# copy from https://cipherli.st
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

# specify cert files
ssl_certificate /etc/letsencrypt/live/ca.chanchun.net/fullchain.pem; # 中间填写你的域名
ssl_certificate_key /etc/letsencrypt/live/ca.chanchun.net/privkey.pem; # 中间填写你的域名
location / {
proxy_pass http://localhost:3000; # 填写你需要转发的服务器地址
}
}

指向静态文件Nginx配置Sample:

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
server {
listen 80;
server_name me.chanchun.net; # 这里写你的域名
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/me; # 这里写你的静态文件目录
}
location = /.well-known/acme-challenge/ {
return 404;
}
return 301 https://$server_name$request_uri;
}

server {
# SSL Configuration
listen 443 ssl;
server_name me.chanchun.net; # 这里写你的域名
# specify cert files
ssl_certificate /etc/letsencrypt/live/me.chanchun.net/fullchain.pem; # 中间写你的域名
ssl_certificate_key /etc/letsencrypt/live/me.chanchun.net/privkey.pem; # 中间写你的域名
location / {
root /var/www/me; # 这里写你的静态文件目录
index index.html index.htm; # 这里写你暴露的静态文件
}
}

Nginx配置各有各的配置方法,这里只要保证四点:

  • 域名配置正确
  • 静态文件目录路径、本地服务目录路径配置正确
  • .well-known/acme-challenge目录配置正确
  • 要保证80默认端口和443ssl端口都有配置

如果配置好Nginx,那么访问网站就会有绿色的小钥匙啦,说明你的https站点搭建好。

自动更新证书

letsencrypt证书最多只有90天,90天之后我们需要重新注册证书,当然这个可以交给服务器自己做啦。

验证自己的证书是否可以更新

1
2
$ cd /opt/letsencrypt
$ ./letsencrypt-auto renew --dry-run

此命令只是验证 不会更新证书

如果出现Congratulations!字样或者已经更新字样则证明可以自动更新。如果出现错误,或者说路径找不到的情况,大多数情况是.well-known/acme-challenge目录配置没有正确的配置成功

编写crontab脚本

1
$ crontab -e

脚本内容

1
30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log

如果遇到权限问题,可先创建/var/log目录再设置其权限为可写入

成功

享受https的绿色小锁吧!

前后端分离开发跨域实践

跨域这个问题可谓是老生常谈,在实际工程开发中也有不同的解决办法。本篇文章来总结跨域的原理和我在前后端完全分离开发中,遇到跨域的解决方法。

跨域的原理

跨域条件

跨域是浏览器限制,目的是为了防止不同域之间访问造成安全问题。何为不同域呢?简单理解,既是不同域名下则产生了跨域。详细包括不同的端口、不同协议、不同子域名、不同域名下都算跨域。具体可以见下面的引用表格:

URL 说明 是否跨域
http://www.a.com/a.js, http://www.a.com/b.js 同一域名下
http://www.a.com/lab/a.js, http://www.a.com/script/b.js 同一域名下不同文件夹
http://www.a.com:8000/a.js, http://www.a.com/b.js 同一域名,不同端口
http://www.a.com/a.js, https://www.a.com/b.js 同一域名,不同协议
http://www.a.com/a.js, http://192.22.45.12/b.js 域名和域名解析ip
http://www.a.com/a.js, http://script.a.com/b.js 两个子域名不相同
http://www.a.com/a.js, http://a.com/b.js 主域名和子域名 是(cookie也无法访问)
http://www.cnblogs.com/a.js, http://www.a.com/b.js 不同域名

由于跨域浏览器的限制,所有来源于一个域内的Js请求在请求其他域时,就会被浏览器拦截下。注意这里的拦截下是指在请求回调过程中。

跨域资源共享(Cross-Origin Resource Sharing,简称 CORS)

既然是跨域是浏览器事件,那么浏览器进行跨域资源共享呢。就要利用CORS机制,跨域资源共享标准新增了一组 HTTP 首部字段:

  • 请求头:

Origin 参数表示发起一个针对跨域资源共享的请求的来源

  • 响应头:

Access-Control-Allow-Origin 表示指定哪一类网站可以跨域资源共享

Access-Control-Allow-Methods 表示服务器允许客户端使用POST, GET 和 OPTIONS 方法发起请求

Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单

Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久

Access-Control-Allow-Credentials 头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容

Access-Control-Allow-Headers 首部字段用于预检请求的响应,其指明了实际请求中允许携带的首部字段

当产生跨域时,就可以通过配置HTTP头部字段来防止请求被浏览器拦截的情况。具体只要在服务器的响应中,设置响应头Access-Control-Allow-Origin和请求头Origin匹配,即可通过CORS机制避免跨域问题。也可以通过设置Access-Control-Allow-Origin为*,表示服务器允许来自所有域的请求。

实际问题

Cookie与Session

在许多业务场景中,往往会存在用户登录态需求,这是通过传统的Session来实现的,而对于Session的客户端记录,大部分的B/S架构会存在于Cookie中,通过Cookie中的SessionID来保留客户端Session记录。

在正常的非跨域请求中,Cookie是可以带入请求头的,那么SessionID会隐藏的通过请求带给服务器,服务器通过SessionID判断是否是保持Session会话的客户端。然而在跨域请求中,即使设置了CORS,请求可以相互通信,但是Cookie还是无法通过请求带给服务端,此时Session会话无法保持。

通过尝试将Access-Control-Allow-Credentials设置为True即可将Cookie带入请求。并且在客户端中要将withCredentials参数设置为true。匹配之后才能传递Cookie。

具体在xhr对象中是以withCredentials参数存在,在fetch中是以credentials: ‘include’;参数存在。同时后台的Access-Control-Allow-Credentials设置也要根据不同的后台框架的HTTP头设置进行调整匹配。

推荐CORS插件

cc-inviewport模块

这个小模块能判断HTMLElement是否在视窗内

关键代码

1
2
3
4
5
const mainOffsetTop = element.offsetTop //元素到顶部距离
const mainHeight = element.offsetHeight //元素高度
const winHeight = window.innerHeight //视窗大小
const winScrollTop = window.document.body.scrollTop + window.document.documentElement.scrollTop //已经卷过的距离
return !(winScrollTop > mainOffsetTop + mainHeight + offset || winScrollTop + offset < mainOffsetTop - winHeight) //返回是否在视窗内的布尔值

目前兼容移动端和主流浏览器,如果有改进欢迎MR

使用方法

Npm直接安装

1
2
3
4

const inviewport = require('cc-inviewport');

inviewport(HTMLElement);

或者使用build中的源代码

1
2

cc_inviewport(HTMLElement);

cc_inviewport挂载在Window下

源码地址:源码

喜欢给个Star,欢迎贡献改进

腾讯实习记事

大致

4月拿到腾讯的实习offer,从7月初到10月底在腾讯实习了4个月。

岗位:IOS开发工程师(从前端转过来)

部门:CDG国际业务部(IBG)

工作

负责VOOV客户端的学习和开发。团队是采取快速迭代开发,基本上每两三周完成一个小版本(约4到5次小迭代)。整体的节奏还是小步快跑,快速实现需求。

VOOV介绍

VOOV是一个面向海外的直播App,目前在香港、马来、印尼、泰国、南非等国家以及地区上线。

完成的需求(时间顺序)

  • 随机UITableViewController
  • 短视频搜索
  • 增加VOOV链接和更多分享渠道
  • 多人视频分享、邀请、以及私信
  • 直播间测速

学习总结

  • 成熟团队开发流程。对比学校或者是年轻团队,腾讯更多带来的是一种规范。调研分析->需求评审->产品提单->交互UI->编码(客户端、Web、接入层和后台快速迭代)->测试(回归)->运维(上线)->Local本地团队动态配置->上报(数据分析)。切身体会到项目管理展现出来的执行力。除此之外,也能体会各个团队之间的合作,沟通。
  • 项目架构。大前端时代,很多东西其实是相通的,在设计模式上,VOOV使用MVVM,分离ViewModel和LogicModel,使Logic层和UI层分离,再通过UI层Handler和Logic层Server链接。这些概念其实在前端领域例如Vue中也有体现。
  • 可持续性?代码统一命名规范,目录结构,Wiki,注释,稳定性,代码可读性,协议规范。
  • 直播技术。直播端RTMP推流,OBS,CMS配置系统,房间功能,帧率码率,弱网络下逻辑。
  • 国际化。翻译、多语言样式设计,多国的Local团队合作。

想说的话

  • 成就感。当自己做的需求交付Apple审核再上线给百万用户使用时,还是有一份成就感。这就是大公司给平凡的程序员的最大魅力。
  • 学习。在项目中学习,在项目中成长是最快的。刚来IBG的时候真的只是一个只看过两个星期Object-C的IOS小白,在VOOV中留下自己的脚印,这是四个月前我想都不敢想的。这是在VOOV项目组最宝贵的进步和收获。
  • 方法。
    • 一定要带着问题去找答案,有一个想法自己尝试去做,做出来了之后再对比吸收尝试别人的方案。
    • 学会给多的时间去深挖,不要仅仅局限于API层级的东西。
  • 娄子。哈哈在实习中差点没少捅娄子,把提测代码给git revert掉。还试过给翻译提3份错误代码。
  • 感谢。感谢导师 Justin 和组长 Grayson还有帮助过我的所有终端组童鞋们。

最后

送给腾讯那段努力的岁月。

最近在干嘛?

已经是大三下学期了5月份了,终于有时间清闲下来写写东西了。

从暑假1月中到现在,找实习已经3个多月了,也紧张了三个多月。最终尘埃落定最终暑假实习去腾讯CDG做Web前端或者移动端的开发。

可能之后就开始准备实习的东西了,熟悉腾讯CDG实习的技术栈,同时完成自己之前计划的学习路线。

春招的总结将会在下一篇的博文中推出。也请期待吧。

cc-audiobuffer 2.x 版本

Gayhub 源码与2.x文章 喜欢给个Star !

不断完善更新中

1.x版本文章: 文章

更新功能

  • 当前播放语音暂停
  • 当前播放语音继续播放
  • 清空当前的语音队列

使用请看 源码Markdown

源码的升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_playNext() {
const _this = this
if (this._isHasBuffer() && !this._isHasCurrentAudio()) {
this.currentAudio = this._shiftBuffer()
this.currentAudio.play()
} else if (!this._isHasBuffer()) {
this.currentAudio = null
}
if (this._isHasCurrentAudio()) {
this.currentAudio.addEventListener('ended', function() {
_this.currentAudio = null
_this._playNext()
}, false)
}
}

解释

_playNext() 函数无论在一开始播放和暂停之后继续播放都要调用。

如何区分当前时一开始播放还是暂停后的继续播放呢?

利用this._isHasCurrentAudio 判断当前是否有载入语音,如果有就是暂停后继续播放,反之就是一开始的初始播放。

接下来即将要实现的功能

  • 接下来的计划:单例模式、报错处理、暂停播放继续播放清空队列优化

【小玩】cc-audiobuffer 一个切片语音拼接工具

源码在最后,喜欢就给个Star !

萌新跪求优化指导,请Forks

萌新瑟瑟发抖

话说在那么紧张的大三快要找实习了而自己还在玩这些真的好吗?T_T

这个小工具是我在最近解决项目问题中制作的。

背景是这样的:作为前端的萌新,被主程老大们吓得瑟瑟发抖。语音实时通讯!这个是最近项目中的一个里程碑,也是一个难点所在。主程老大们的想法是,浏览器录音,切片,通过服务器转存再通过广播Url的方式进行转发。使得各个客户端都可以实时通讯。

解决方法:大神 muaz-khan WebRTC中的 MediaStreamRecorder 进行浏览器录音和切片(膜拜),使用七牛云 存储,转发并获得切片语音的Url!最后最后,就是拼接语音啦,所以就产生了这个cc-audiobuffer工具。

直接看实现

cc-audiobuffer采用了ES6封装类来实现,并且使用babel转义。

连续播放的实现

  • 递归的思想,只要缓冲区中还有Audio,就继续播放。
  • 事件监听,监听Audio对象中的 ended 事件作为下一段语音播放的标志。
1
2
3
4
5
6
7
8
9
10
11
12
play() {
const _this = this
if (this.isHasBuffer()) {
this.currentAudio = this.shiftBuffer()
this.currentAudio.play()
_this.currentAudio.addEventListener('ended', function () {
_this.play()
}, false)
}else{
this.currentAudio=null
}
}

语音提前加载的实现

  • 在语音的Url加入缓冲区的时候,使用Audio对象的preload属性实现。
1
2
3
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)

动态语音加载的实现

  • CCAudioBuffer有pushBuffer('url') 方法,如果缓冲区有Audio正在播放的话,此方法将新的Audio加入队列,如果缓冲区中没有Audio播放,将会将Audio加入缓冲区同时自动播放新的Audio。
1
2
3
4
5
6
7
8
9
10
11
12
13
pushBuffer(url) {
if(!this.isHasCurrentAudio()){
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)
this.play()
}
else{
let audio = new Audio(url)
audio.preload = "auto"
this.AudioUrlBag.push(audio)
}
}

想试一下吗?

安装

npm:

1
npm install cc-audiobuffer
1
import CCAudioBuffer from 'cc-audiobuffer'

script:

1
<script src="dist/cc-audiobuffer.js"></script>

使用

无参数创建

1
2
3
let CCAudioBuffer = new CCAudioBuffer()

CCAudioBuffer.pushBuffer('url')

当有新的url语音地址传过来的时候,CCAudioBuffer会提前加载并且自动播放语音。不管何时调用pushBuffer('url'),这些语音都会依次进入缓冲区且连续播放直到缓冲区为空。

有参数创建

1
2
3
4
5
let CCAudioBuffer= new CCAudioBuffer([new Audio('url'),new Audio('url'),new Audio('url')])

CCAudioBuffer.play()

CCAudioBuffer.pushBuffer('url')

当你有参数创建时,参数规定是Audio对象的一个数组。

接下来需要调用play() 接口保证参数内的Audio播放。

pushBuffer() 的作用和无参数创建一样。

反思一下

最近真是忙成象拔蚌,这个工具做的很急,自我安慰下。

源码很短很短,也没有考虑到对象内部的一些私有封装什么的。还是想以后有时间能重写一下。

也写写些请求啥的,希望请教大家,问问大家有没有优化方案什么的,也给小生我上门课。

最后这里是Gayhub源码

喜欢给个Star!指教给个Forks!

戳我

细胞自动机

备注:文末有自己用Javascript简单实现的网页版细胞自动机(还挺好玩)

什么是细胞自动机

Imgur

细胞自动机(英语:Cellular automaton),又称格状自动机元胞自动机,它是由无限个有规律、坚硬的方格组成,每格均处于一种有限状态。每格于t时的态由t-1时的一集有限格(这集叫那格的邻域)的态决定。每一格的“邻居”都是已被固定的。每次演进时,每格均遵从同一规矩一齐演进。

当然这个细胞自动机有一个游戏实现 ——康威生命游戏(英语:Conway’s Game of Life)

康威生命游戏规则

生命游戏中,对于任意细胞,规则如下:
每个细胞有两种状态-存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。(如图,黑色为存活,白色为死亡)

  1. 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
  2. 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
  3. 当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
  4. 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)

可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后, 可以得到第一代细胞图。按规则继续处理当前的细胞图,可以得到下一代的细胞图,周而复始。

康威生命游戏的自由

有了核心的算法(游戏规则),康威生命就是一个具有生命的自由游戏。你可以在游戏中创造出自己的细胞世界。周而复始。

Conways_game_of_life_breeder_animation

自己实现的一个生命游戏

生命游戏其实并不是很复杂,自己实现一个还是挺好玩的。所以自己就用Vue实现了一个小小的生命游戏

核心思想

  • 下一步要做什么,生命游戏最重要就是下一步,不管游戏规则是如何,下一步状态是这个游戏发展的动力。

    1
    2
    3
    4
    5
    function nextStep(map) {
    let newMap;
    //@TODO对newMap进行核心算法的编写
    return newMap;
    }

    此函数就是游戏的生命,将游戏规则编写进这个函数,Vue只负责渲染这个newMap就好了。

所以我们有以下函数

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
function nextStep(map) {
let newMap = Array(map.length).fill(Array(map.length).fill(0)).map((i) => i.map((j) => 0));
let countAlive = 0;
for (let i = 0; i < map.length; i++) {
for (let j = 0; j < map.length; j++) {
countAlive =
getMap(i + 1, j, map) +
getMap(i - 1, j, map) +
getMap(i, j + 1, map) +
getMap(i, j - 1, map) +
getMap(i + 1, j + 1, map) +
getMap(i + 1, j - 1, map) +
getMap(i - 1, j + 1, map) +
getMap(i - 1, j - 1, map);
if (map[i][j] === 0 && countAlive < 3) {
newMap[i][j] = 0;
}
if (map[i][j] === 1 && 2 <= countAlive && countAlive <= 3) {
newMap[i][j] = 1;
}
if (map[i][j] === 1 && (countAlive > 3 || countAlive < 2)) {
newMap[i][j] = 0;
}
if (map[i][j] === 0 && countAlive === 3) {
newMap[i][j] = 1;
}
countAlive = 0;
}
}
return newMap;
}
  • 边界问题,最外层肯定都是没有细胞的,所以我们做了边界判断函数。
1
2
3
4
5
6
function getMap(i, j, map) {
if (i >= map.length || j >= map.length || i < 0 || j < 0) {
return 0;
}
return map[i][j];
}
  • 文件导出导入 当然加了扩展功能,保存自己喜欢的细胞自动机成文件。

  • 速度调整 扩展功能,调整下一步的速度。

实现的效果

img

Github源代码,喜欢就给个Star。细胞自动机