Django-Vue搭建个人博客:项目部署
9496 views, 2021/04/02 updated Go to Comments
我们的博客虽然还有不完善的地方,但是没关系,越早把它部署到互联网上,才能越早发现线上特有的问题,让产品在迭代中成长。
部署考验的是你对 Linux 的操作能力,以及对网络通信的理解。加油哟。
注意:以下流程经过笔者验证,能够保证顺利部署项目。如果你不知道每一步都是干嘛的,那么请严格按照文章的流程顺序操作。
如果你卡在某些地方动弹不得,一定要多找资料,尝试理解当前指令或报错的意义,用智慧克服障碍,这就是学习的意义。
配置服务器
要架设网站,首先你要有一台连接到互联网的服务器。国内比较出名的云服务器属阿里云、腾讯云、百度云,三家各有优劣,大家自行了解比较,并选择自己适合的购买。
利益相关:笔者自己用的是阿里云,所以教程会以阿里云ECS作为例子讲解。新用户通过推广链接注册有折扣和现金券;学生有优惠服务器每月9.5元,很划算。
阿里云服务器购买页面变动频繁,如果图中展示的步骤和你购买时的不一样也没关系,核心步骤都是差不多,稍微找一下就OK了。
首先进入阿里云ECS的购买页面:
图片字很小,看不清楚的同学将就一下放大看吧。
挑重点说一下:
- 实例从入门级里选一款便宜的,以后流量高了再升级也不迟(土豪请无视这条)。
- 镜像选择 Ubuntu 。其他 Linux 版本也是可以的,根据你的使用习惯确定。
- 系统盘先选个 20G,够你用一阵了。数据盘暂时用不上,不用勾选。
点击下一步,来到网络和安全组页面:
这页默认就行了,公网带宽选最低的 1M ,初期够用了。
如果有询问是否购买公网 IP 的选项,记得勾上。没公网 IP 就没办法连接到互联网了。
点击下一步,到系统配置页面:
为了后面远程连接服务器更简单,这里勾选自定义密码,也就是输入用户/密码的认证方式了。实际上秘钥对的认证方式更安全些,以后摸熟了再改回来吧。
点击下一步,到分组设置页面。这个页面全部默认设置就好了。点击下一步,确认订单无误后,就可以付款啦。
付款成功后,通过控制台就可以看到已购买的云服务器了:
这里有时候会有黄字提醒你服务器的网络端口没开,点击黄字链接去开通一下:
把 22(远程连接端口)、443(HTTPS端口)、80(HTTP端口)都打开,3389端口顺便也开了。
至此服务器的购买、配置就完成啦。稍等几分钟后等待初始化完成,就可以得到服务器的公网 IP 地址,博主的是 114.215.169.69
,后面会用到。
准备工作
在正式部署前,还有些准备工作需要做。
前端文件打包
开发时为了方便(热更新、支持 .vue 文件等功能),用到了专门的前端服务器。部署到正式环境中时就不再需要前端服务器了,而是将代码打包为 js 、 css 等静态资源。因此就需要将前端文件打包。
打包很简单,如果你是跟着教程一步步来的,直接命令行进入 frontend/
目录,执行:
# 本机命令行 # 路径位于 frontend 目录 D:\...\frontend> npm run build
命令执行完毕后多出个 dist/
目录,里面就是前端部署所需要的所有资源,比如 js 、 css 和 html 等。
需要说明的是部署的方法很多种,本文只介绍了其中一种。根据实际需求,在正式环境中也可以前后端分别启动自己的服务器,做到服务器意义上的”前后端分离“。
修改后端配置
前端打包好后,Django 的配置也要相应更改为线上状态:
# drf_vue_blog/settings.py ... # 修改项。关闭调试模式 # 关闭后 django 不再处理静态资源 DEBUG = False # 修改项。允许所有的IP访问网络服务 ALLOWED_HOSTS = ['*'] # 修改项。指定需要收集的静态文件的位置 # 即前端打包文件所在位置 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "frontend/dist/"), ] # 新增项。静态文件收集目录 STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static')
保险起见,然后在虚拟环境中执行:
(venv) > pip freeze > requirements.txt
确保 requirements.txt
含有后端所有的依赖库。
最后保存文件,将修改更新到 Git 中。
代码上传Github
通常我们会将项目上传到 Github ,再由 Github 将项目代码下载到服务器。因此你还需要把项目上传到 Github。
鉴于国内网络环境复杂,Github 经常会速度很慢或无法登录。这时候你也可以尝试用国内的 Gitee,或者直接点对点本地上传服务器。
如何上传这里就不细讲了,请自行学习 Git 相关知识,注册 Github 账号等。
需要提醒的是,一定要清楚项目中哪些文件上传,哪些文件不用传:
- 前后端所有用到的库都不要上传,比如 venv 目录和 node_modules 目录,这些依赖项可以在服务器中很方便地安装;
- 前端打包目录
dist/
是需要上传的,因此要注意去掉项目各级目录中的.ignore
文件里的/dist
行,确保dist/
目录被收录到 Git 中。
接下来就是正式部署了。
远程连接
部署的第一步就是想办法连接到云服务器上去,否则一切都免谈。鉴于项目是在 Windows 环境开发的,推荐用 XShell 来作为远程连接的工具。XShell 有学校及家庭版本,填一下姓名和邮箱就可以免费使用了。千万别嫌麻烦去下载来路不明的“绿色版”、“纯净版”,万一有木马你哭都哭不出来了。
XShell 怎么使用就不赘述了,以读者的聪明才智,稍微查阅一下就明白了。
使用相当简单,基本就是把主机 IP、端口号(22)以及登录验证填好就能连接了。
连接成功后,就能在 XShell 窗口中看到阿里云的欢迎字样了:
Connecting to xxx... ... Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-58-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage Welcome to Alibaba Cloud Elastic Compute Service ! root@dusai:~$
root@dusai:~$
是命令提示符(root 是用户名,dusai 是主机名),输入命令时不需要你输入这个。
本文后面把
root@dusai:
字符省略掉,方便大家阅读。
安装项目及依赖
接下来的部署指令均在服务器中执行,也就是在 XShell 中操作,别搞混了。
首先更新系统库:
~$ apt-get update ~$ apt-get upgrade
部署到正式环境时,后端服务器就不能用 Django 自带的开发服务器了(性能低下),而是改用 Nginx 和 Gunicorn 配合提供网络服务。
- 客户端发来 http 请求,Nginx 作为直接对外的服务器接口,对 http 请求进行分析;
- 如果是静态资源请求,则由Nginx自己处理(效率极高);
- 如果是动态资源请求,则把它转发给 Gunicorn 进行预处理后,转发给 Django,最终完成资源的返回。
除此之外,还要确保 Python3 、Git 和 virtualenv 也都正确安装。
顺序执行以下指令:
~$ apt-get install nginx ~$ apt-get install python3 ~$ apt-get install python3-pip ~$ apt-get install git ~$ pip3 install virtualenv
均成功后,创建并跳转到项目目录:
~$ mkdir -p /home/sites/myblog ~$ cd /home/sites/myblog # 进入的路径如下所示 /home/sites/myblog$
接下来就可以从 Github 下载项目了:
# 以教程仓库为例 django-vue-tutorial
../myblog$ git clone https://github.com/stacklens/django-vue-tutorial.git
这里就以教程的仓库为例,读者用自己项目时一定要注意路径名称正确。
如果你是从非公开项目下载,用户名密码的认证方式 Github 已经准备废弃了。如遇报错请以密钥认证的形式下载。
下载好项目后,在同级路径创建并进入虚拟环境:
../myblog$ virtualenv --python=3.8 venv ../myblog$ source venv/bin/activate # 看到 (venv) 开头就对了 (venv) ../myblog$
进入项目目录,安装依赖、收集静态资源并迁移数据库:
(venv) ../myblog$ cd django-vue-tutorial (venv) ../django_vue_tutorial$ pip3 install -r requirements.txt (venv) ../django_vue_tutorial$ python3 manage.py collectstatic (venv) ../django_vue_tutorial$ python3 manage.py migrate
最后启动 nginx:
# 为了阅读方便,后续命令行均省略 $ 前面的路径部分 (venv) ~$ service nginx start
在浏览器中访问你的云服务器的公网 IP ,看看效果
看到 Nginx 的欢迎页面则成功一半了。继续。
配置nginx
Nginx 欢迎界面这个默认配置显然是不能用的,所以需要重新写 Nginx 的配置文件。 /etc/nginx/sites-available
目录是定义 Nginx 可用配置的地方。输入指令创建配置文件 myblog
并打开 vim 编辑器:
(venv) ~$ vim /etc/nginx/sites-available/myblog
关于 vim
编辑器如何使用也不赘述了,这里只说两个最基本的操作:
- 按
i
键切换到编辑模式,这时候才可以进行输入、删除、修改等操作 - 按
Ctrl + c
退回到命令模式,然后输入:wq + Enter
保存文件修改并退回到服务器命令行
回到正题,用 vim
在 myblog
文件中写入:
server { charset utf-8; listen 80; server_name 114.215.169.69; # 改成你的 IP # 定义 server 的根路径 # 修改为你的项目的路径 root /home/sites/myblog/django-vue-tutorial; # 以下4项都是在给静态资源配置转发路径 # 注意路径名称一定要正确 # 特别是中横线 - 和下划线 _ 别弄混了 location /static { alias /home/sites/myblog/django-vue-tutorial/collected_static; } location /media { alias /home/sites/myblog/django-vue-tutorial/media; } location /js { alias /home/sites/myblog/django-vue-tutorial/collected_static/js; } location /css { alias /home/sites/myblog/django-vue-tutorial/collected_static/css; } # 将接口及后台请求转发给 Gunicorn location ~ (^/api|^/admin) { proxy_set_header Host $host; proxy_pass http://unix:/tmp/114.215.169.69.socket; # 改成你的 IP } # 其他所有请求均直接请求 Vue 打包的 html 文件 location / { try_files /collected_static/index.html =404; } }
此配置会监听 80 端口(通常 http 请求的端口),监听的 IP 地址写你自己的服务器公网 IP。
配置中有3个核心规则:
- 如果请求静态资源,则转发到对应目录中寻找静态资源
- 如果请求接口数据或后台页面,则转发给 Gunicorn
- 其他请求则直接请求 Vue 打包的前端文件
如果你已经申请好域名了,就把配置中有 IP 的地方都修改为域名,比如:server_name www.dusaiphoto.com。
写好后就退出 vim
编辑器,回到命令行。因为我们写的只是 Nginx 的可用配置,所以还需要把这个配置文件链接到在用配置上去:
(venv) ~$ ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled
至此 Nginx 就配置好了,接下来搞定 Gunicorn
。
有的读者无论怎么配置都只能看到 Nginx 欢迎页面,有可能是 sites-enabled 目录中的 default 文件覆盖了你写的配置。将 default 文件删掉就可以正常代理自己的配置文件了。
Gunicorn及测试
Nginx 搞定后就只剩 Gunicorn 了。
下面的三条命令分别是安装 Gunicorn 、 重启 Nginx 和 启动 Gunicorn:
(venv) ~$ pip3 install gunicorn (venv) ~$ service nginx restart # 将 IP 改为你的公网 IP # .wsgi 前面为 Django 配置文件所在的目录名 (venv) ~$ gunicorn --bind unix:/tmp/114.215.169.69.socket drf_vue_blog.wsgi:application # Gunicorn 成功启动后命令行提示如下 [2021-02-21 13:12:10 +0800] [35355] [INFO] Starting gunicorn 20.0.4 [2021-02-21 13:12:10 +0800] [35355] [INFO] Listening at: unix:/tmp/114.215.169.69.socket (35355) [2021-02-21 13:12:10 +0800] [35355] [INFO] Using worker: sync [2021-02-21 13:12:10 +0800] [35357] [INFO] Booting worker with pid: 35357
Gunicorn 就启动成功了。
接下来用浏览器访问试试:
大功告成啦,撒花庆祝!
注意:本项目在部署后会出现一个麻烦的 Bug:文章列表翻页后会高频重复请求后端接口。解决方法见本文末尾。
收尾工作
后期运维
你的网站是需要不断更新优化代码的。每次修改代码后,更新到服务器上也很简单。在虚拟环境中并进入项目目录,依次(collectstatic 和 migrate 是可选的)执行以下命令:
git pull
python3 manage.py collectstatic
python3 manage.py migrate
# 重启 gunicorn
pkill gunicorn
gunicorn --bind unix:/tmp/114.215.169.69.socket my_blog.wsgi:application
加上 cd
更改目录的指令,部署过程有十几条指令,手动输入也太麻烦了。简单粗暴的办法是利用 XShell 的宏,把部署指令写成顺序执行的脚本,点几个按钮就完成了,非常方便。
更高级的做法是在服务器上编写自动化部署的脚本,这个就读者以后慢慢研究吧。
如果你更改了 Nginx 的配置文件,还需要重启 Nginx 服务:
service nginx restart
域名及优化
相对部署来说,域名配置就很容易了。阿里云提供域名的购买、备案(顶级域名必须,约10个工作日)、解析服务,简直全家桶有没有。重点提醒有了域名之后要改的地方:
Nginx
中与 IP/域名 有关的位置Gunicorn
中与 IP/域名 有关的位置
域名搞定之后,接着就可以着手考虑把网站升级为 https 版本了,这是大趋势,一定要做。(这个也留给读者自己折腾)
另外,开发时为了效率把所有静态资源都下载到本地,但是部署时不推荐这样做,原因是静态文件通常体积都较大,你花血汗钱买的服务器载入会很慢。尽量远程 CDN 调用,像这样:
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
国内推荐BootCDN,速度快还免费。
媒体资源也是类似,小图还无所谓,大图就要放在七牛云这类的对象存储云上,否则你网页的载入速度会很悲剧的。
最后,在开发时我们往 settings.py
中写入如 SECRET_KEY 、邮箱密码等各种敏感信息,部署时千万不要直接上传到公开仓库,而是把这些信息写到服务器本地,然后在 settings.py
中读取。
进程托管
部署过程中还有个新手经常碰到的问题,就是当 SSH 终端一关闭,Web 服务也一起被关闭了,导致网站无法连接。这个问题在 @frostming 的文章 《Web 服务的进程托管》 中用了三种常见方法解决了,并且还实现了异常重启和开机自启动。有类似疑惑的同学可以前往围观。
Bug修复
不管怎么小心,开发环境和线上环境多少还是有些区别(比如线上请求是有延迟的,而本地开发几乎没有),这些区别就造成了某些部署之后才可能出现的 Bug 。比如本项目这个Bug:文章列表翻页后会高频重复请求后端接口。
推测是由于 watch(route) 对路由变化的深层监听、结合线上请求延迟的特性造成的。
一个临时的解决方案是在 Vue 实例中新增一个状态,用以判断当前请求是否重复。若确认是重复请求,则立即中断此请求。
修改 ArticleList.vue
代码:
// frontend/src/components/ArticleList.vue setup() { ... // 新增响应式对象 const kwargs = ref({page: 0, searchText: ''}); // 修改方法,把此对象作为第三个参数 getArticleData(info, route, kwargs); ... return {...} }
接着修改 getArticleData.js
代码:
// frontend/composables/getArticleData.js export default function getArticleData(info, route, kwargs) { const getData = async () => { // 函数起始处对当前 query 参数进行等值判断 // 若为重复请求则立即中断函数 const queryPage = route.query.page !== undefined ? parseInt(route.query.page) : 1; if (kwargs.value.page === queryPage && kwargs.value.searchText === route.query.search) { return } ... // 已有代码 info.value = response.data; // 函数末尾对当前页的状态赋值 // 用于下次请求时进行重复性判断 kwargs.value.page = queryPage; kwargs.value.searchText = route.query.search; }; ... }
给项目打了个补丁。更新代码到服务器并重启服务,此问题解决。
所以部署完成之后,也还是要仔细检查下各功能是否正常啊。
总结
部署可以说是入门者最大的难关了,也是检验成果、获取成就感的关键一步。
多查资料,要相信你遇到的问题别人早就遇到过了。
路漫漫其修远兮,吾将上下而求索。