Blog 第一阶段开发总结
一、开发简述
博客第一阶段开发从2025年4月14日开始,前端开发从14日到24日,后端开发从24日到27日,部署上线阶段从27日至本文开始撰写(29日)还未完成。
前端采用Vue架构,大量使用Element组件的情况下仍然不能开发出令人满意的效果,在可预见的即将花费过量的时间在前端开发的情况下,我选择大幅度减少功能与交互体验仅仅保留完成了核心的功能以希望能够使项目顺利推进。前端的具体开发情况以及改善方法在后续日志中再集中整理总结。
后端采用Django框架与 Django Rest Frame Work 接口, 数据库则采用Django自带的轻量级SQLlit3, 由于Django 框架有着 /admin 这一对于数据库进行增删改查的后台视图,暂时被我用来替代原本计划的Blog后台,由此后台编写的复杂程度大幅度降低,由于只需要专注于数据的返回形式,开发过程异常的顺利与迅速。针对Blog后台,由于 Blog 管理端的开发要置于前端的完善之后,现阶段开发的时间现在还不可预测。
最后是网站的部署,情况比预想中艰难,在这里进行详细的记录。
本网站最终希望实现的效果是Django+Vue+Nginx的一个容器组在docker上运行。但是Django从开发环境投入到生产环境需要考虑到服务的并发等因素,所以常用的Django部署采用 Django+Nginx,故而部署方案修改为 Django+djangoNginx+vueNginx。Django的服务通过 gunicorn 启动,前端的请求发往djangoNginx再被转发到后端。前端Vue框架则是直接 build 打包之后部署到 Nginx 服务器。
上述服务部署过程的修改是遇到的第一个问题,第二个问题则在于我希望容器组运行在我的Mac主机之上保证服务的稳定性。这涉及到了镜像的编译问题,我的程序是在 Windows主机上开发的,由于芯片架构和系统架构的区别,同一镜像不可能在两个系统中都成功运行。第一个方案是在Windows主机上编译镜像时指定芯片架构,第二个方案是将程序导入Mac主机,在Mac主机上进行编译。由于方案一可能产生的不可预测同时难以分析的错误,最终决定采用方案二。
二、部署过程
将程序直接复制到Mac主机运行的过程并不顺利,一直报错缺少一个无法安装的库。思考这个库应该与接口文档的代码相关,所以一个方案是删除接口文档相关的代码(因为接口文档并非Blog逻辑的刚需),另一个方案是在Mac主机上重写代码。最终选择了后者,原因有二,其一是认为存在接口文档对于后端的管理更好(部署成功后发现接口文档始终难以访问,疑似是生产环境与开发环境的区别导致,所以接口文档相关代码似乎不应该存在于准备部署的后端代码中),其二是由于后端开发过程过于追求速度,存在一些项目结构的不合理,注解的不清晰与多余的功能逻辑,为方便后续升级更新,决定重新组织一遍后端项目。
2.1 虚拟环境的创建
凡是需要部署的python项目,拥有一个单独的python虚拟环境都是必要的。
在这里贴上虚拟环境的创建方法。
# 在创建虚拟环境的位置打开控制台
python -m venv [虚拟环境名称]
# 激活虚拟环境,激活后安装所需库包
.\[虚拟环境名称]\Scripts\activate
# 关闭虚拟环境
deactivate本项目经过总结需要运行的库包安装代码仅有两条(注意:并非只安装两个库,因为安装部分库时他会一起安装所依赖的库)
# 安装 Django Restful 接口框架,它会一起安装Django以及其它所依赖的库包
pip install djangorestframework
# 安装 Django 的跨域访问支持库包,需要正式部署的项目都需要,特别是对于前后端分离的项目
pip install django-cors-headers还有关于生成接口文档的库包,这里仅列举本项目所用的,实际可选择不同库包来生成不同风格的接口文档。接口文档对于测试接口有很大的帮助,但是也可以使用postman这一软件方案代替,所以并非刚需。
pip install PyYAML
pip install coreapi
# coreapi需要依靠setuptools中的一个库,需要我们手动安装
pip install setuptools2.2 Django项目重写
在搭建好虚拟环境,安装好必要的库包之后开始创建项目,创建项目时选择编译器为刚创建的虚拟环境。
2.2.1 项目准备工作
进入项目,配置 setting.py
# 安装的应用
INSTALLED_APPS = [
'corsheaders', # 跨域支持,位置必须第一个
'django.contrib.admin', # Django自带的管理端支持
...
'rest_framework', # Rest ful 接口支持
...
]
# 中间件
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 跨域支持中间件,至少在 common 中间件之前
...
'django.middleware.common.CommonMiddleware',
...
]
# 时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
# 静态文件夹
'''
此处在代码中访问静态文件我需要 /static/...
而静态文件实际的存放位置是 /staticfiles
'''
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # 不写则默认 /static
# DRF 的全局配置
REST_FRAMEWORK = {
# 接口文档类型,没有接口文档则不添加
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 时间格式,不写也行
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S',
'DEFAULT_PAGINATION_CLASS': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
],
'''
权限设定
IsAuthenticatedOrReadOnly:非登录用户只能访问安全方法,比如 get
IsAdminUser:仅有管理员拥有操作权
IsAuthenticated:仅有登录用户拥有操作权
权限设定还可以作用到特定视图,以获得更灵活的权限分配控制
'''
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
'''
身份认证设置:
BasicAuthentication:账号密码认证
TokenAuthentication:token认证
SessionAuthentication:会话认证
'''
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}在配置好设定文件后生成数据表,迁移数据库
python manage.py makemigrations
python manage.py migrate创建管理员账户
python manage.py createsuperuser启动项目,访问 /admin
2.2.2 开始重写项目
对于项目代码的具体说明将整理在其他日志中在此只给出流程。
创建app并注册
python manage.py startapp [appname]创建模型类 ./model中操作
将模型类注册到 admin 后台中 ./admin中操作
迁移数据库
启动项目, 访问 /admin
对数据库进行增删改查,检查数据库是否符合要求
创建 serializers.py 在这个文件中构建序列化类,对数据进行序列化与反序列化
创建视图
创建路由
使用接口文档或者postman,检测路由是否正常工作
后端重写完成
2.3 后端项目的部署
诚然,最后需要前后端一起部署,构成一个容器组。但是由于后端部署方式的改变,现阶段先单独部署后端方便对过程中的错误进行分析与解决。
2.3.1 从开发环境到生产环境
开发环境到生产环境的转变并非仅仅只是一个部署的过程,这其中还需要部分配置。
setting.py中调整 DEBUG字段为**False**, 这是为了避免在生产环境中由于错误操作的报错导致泄露代码细节或项目结构的重要操作。
setting.py中配置 CORS_ALLOWED_ORIGINS、CSRF_TRUSTED_ORIGINS字段,确保前端地址可以顺利访问后端数据
CORS_ALLOWED_ORIGINS = ["http://[ip or domain]:[port]"]
CSRF_TRUSTED_ORIGINS = ["http://[ip or domain]:[port]"]2.3.2 部署前的补充——静态文件的收集
本来开始没有这一步骤的,但是我第一部署成功后访问 /admin 发现没有了 CSS。我还以为是我的部署过程出现了什么错误。最后发现是Django的后端在投入生产环境之后就不会再操作静态文件,他将这些工作全部交给了前端,他只负责数据的处理。
这对于需要依靠Django的后台系统的我的影响是非常大的。搜查之后发现,需要将Django 的静态文件先收集起来,然后交给Nginx,这样,代理之后Nginx就会自然的帮助处理这些静态文件(CSS)。
收集过程非常简单,只有一段代码,这些静态文件会收集在静态文件夹中,后续配置时会用到这些文件。
# 将所有静态文件收集到静态文件夹下
python manage.py collectstatic2.3.3 Dockerfile
然后是重要的docker镜像的配置文件dockerfile
需要注意的是这里项目启动的命令 gunicorn [项目名称].wsgi:application --bind,现在还不是特别理解,反正说是考虑到生产环境与开发环境的区别这样启动比 python manage.py runserver 要更好。不过这样也导致需要再安装一个库包。
pip install gunicorn# 基础环境
FROM python:3.9.6
# 作者标签
LABEL authors="Hiko"
# 构建镜像的工作地址
WORKDIR /app
# 复制Python库环境并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制Django项目
COPY . .
# 设定项目端口
EXPOSE 8000
# 设定项目启动命令
CMD ["gunicorn", "blogDjango.wsgi:application", "--bind", "0.0.0.0:8000"]2.3.4 构建 Docker Image
开始构建之前确认一遍所有项目配置特别是 setting.py 以及 requirements.txt。
这里贴出导出 requirements.txt 的代码
pip freeze > requirements.txt所有文件准备好后开始构建 Docker Image, 注意代码后有一个点
docker build -t [ImageName] .创建完成之后可以在 docker desktop 中或者通过 docker images 代码查看镜像。
2.3.5 创建启动容器的配置文件
启动容器是关键的一步,这涉及到配置文件的控制,容器交互控制等关键的因素。
对于多容器,我们使用 docker-compose.yml 来进行配置
services:
# django容器启动配置
[容器名]:
image: [使用的镜像]
container_name: [容器名]
volumes:
# 容器目录挂载配置 【重要】
# - [宿主机位置]:[需要挂在的文件夹或文件的位置]
# 将容器的数据库文件夹以及博文文件夹挂载出来,对数据进行持久化操作,方便数据的存储与备份
- /Users/hiko/Documents/BlogData/db_data:/app/database
- /Users/hiko/Documents/BlogData/md_data:/app/markdown
# 设定重启模式
restart: always
environment:
- DJANGO_SETTINGS_MODULE=blogDjango.settings
# nginx容器启动配置
django_nginx:
image: nginx:latest
container_name: django_nginx
# 容器对外暴露的端口,将自身的80端口映射到宿主机的44400端口
ports:
- "44400:80"
volumes:
# 将静态文件夹挂载到宿主机上,读取Django的静态文件
- /Users/hiko/Documents/BlogData/django_data/staticfiles:/staticfiles
# 将配置文件挂载到宿主机上,方便宿主机进行配置
- /Users/hiko/Documents/BlogData/django_data/nginx.conf:/etc/nginx/conf.d/default.conf
# 依赖于 blog_django 容器,在此容器之后再启动
depends_on:
- blog_django2.3.6 启动容器前的准备
对于 Django 目录挂载是为了数据持久化,将必要的数据存储在宿主机上方便数据的备份与服务器以后的迁移。所以说,Django 的目录挂载是将数据交出来。
而对于 Nginx 目录挂载是将数据拿进去,他需要 Django 的静态文件数据去渲染 Django 的 /admin 后台,他需要**配置文件**去确定他要代理什么服务,如何代理。所以接下来针对这两个文件进行说明。
Django 的静态文件之前我们已经收集好了(见2.3.2),现在我们只需要将他复制到 nginx 静态文件的挂载位置即可。
Nginx 的配置文件也只需要配置好后复制到挂载的位置即可,这里给出配置文件 nginx.conf
server {
# 服务监听端口
listen 80;
server_name localhost; # 或你的服务器IP/域名
location /static/ {
alias /staticfiles/; # 注意,这里的路径要对应到Nginx容器内部挂载的 staticfiles 目录
}
location / {
proxy_pass http://blog_django:8000; # 转发到 Django 容器(gunicorn)
proxy_set_header Host $host;
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;
}
}2.3.7 启动容器
所有文件准备好后,在 docker-compose.yml 文件所在的位置打开控制台,输入命令启动容器。
docker-compose -p [容器组名称:must consist only of lowercase alphanumeric characters, hyphens, and underscores as well as start with a letter or number] up -d2.3.8 测试服务
容器启动后,打开网页应该无法登录 /admin, 因为这个时候数据库还没有数据
进入容器运行数据表创建与迁移命令,创建管理员账户。这样,后端就应该可以正常使用了。
在开发环境中的接口文档在生产环境无法打开,可能是我选择的接口文档过时或者什么其它原因。但是接口的测试还可以通过 postman 进行,如果需要连接前端测试,一定要注意配置 settings.py中的 CORS_ALLOWED_ORIGINS、CSRF_TRUSTED_ORIGINS 字段,如果镜像创建之后需要修改setting.py有两个方案,一个是将 settings.py文件挂载出来,第二个是进入容器内操作文件。
至此,后端部署的全部流程结束了,接下来进入,前后端联合部署。
2.4 前后端联合部署
由于前端项目比后端复杂很多且前端项目代码并不有强烈的“系统依赖”。所以前端的镜像构建方案是,在Windows主机上打包好之后,在Mac主机上编译为镜像,再运行测试。
现在是4月30日,经过一些验证,上述(在Windows主机上打包好之后,在Mac主机上编译为镜像)的方案不可行,不知原因网页就是无法打开。同样的配置文件在Windows主机上就可以实现,判断还是系统架构或者芯片架构的差异导致的。所以改为执行在Mac主机上构建一遍程序再打包的方案。出人意料地是Vue3项目在Mac主机上的迁移非常顺利,我仅仅通过npm安装了所有的所需模块后,直接复制了src文件夹,然后重新配置了vite.config.ts 就使得程序可以正常运行了,这确实是意外的惊喜,我以为又要打一场恶仗。后续的打包,创建镜像,运行也十分顺利,现在就可以着手开始进行部署了。
2.4.1 前端部署
单独的前端部署在代码移植到Mac主机并成功运行后基本不存在什么问题,关键在于前后端联合部署需要对 axios、nginx.cong、目录挂载等项目进行细致的配置,一点错误就会导致数据无法返回。
2.4.2 联合部署前的代码修改
部署前的代码修改仅仅只有一处,也就是 axios 的请求地址(baseURL),一开始我填写的地址为 http://django_vue/client , 但我忽视了一个重要的问题,这个问题在部署的验证阶段就已经遇到过但是我却没有记录。http://django_vue/client 这一地址是实现容器间通过容器名进行访问的路径,这得益于docker对同一容器组的容器创建了一个共用的网络。但是将这个地址写在 axios 中他会通过浏览器去分析这个域名,而显然没有接入 docker 网络的浏览器无法识别这一域名,数据自然也就请求不到,甚至请求无法到达后端。
正确的做法是让 nginx 来转发我对于后端的请求,于是axios中我进行了如下修改
// 创建一个axios实例
const service = axios.create({
baseURL: '/blog/client/', // nginx 会转发到真实的后端
timeout: 5000, // 请求超时设置
})此时的 baseURL 并没有代码上的意义,仅仅作为一个请求的路由标识,而在 nginx.conf 中的配置可以将请求转到真实的后端地址。
# 设定 axiox 的访问地址
location /blog/client/ {
proxy_pass http://django_nginx/client/;
proxy_set_header Host $host;
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;
}这一项配置将 http://[前端域名]/blog/client/... 的请求转发到后端的 http://[后端Nginx域名]/client/... , 然后后端的Nginx再将请求转发到Django, 这样才算后端收到了前端的请求。
在后端接收到请求之后,需要满足一定的验证才可以返回数据。第一个验证是 setting.py 中的 CORS_ALLOWED_ORIGINS、CSRF_TRUSTED_ORIGINS 字段需要包含前端地址,这样后端才会允许跨域请求。第二个验证是前端必须要返回真实IP、主机信息等信息,后端验证后才会返回信息,这一步在Nginx中由 proxy_set_header 字段的配置完成。
2.4.3 制作前端镜像
前端的代码准备工作完成之后,就可以制作前端镜像了,此处 Dockerfile的代码量很少,操作也相对简单。
前端的打包
npm run buildDokcerfile
# 基础环境:轻量级前端服务器,nginx
FROM nginx:alpine
# 将打包好的Vue项目(位于dist下),复制到服务器的相应位置
COPY dist /usr/share/nginx/html
# 设定容器监听的端口
EXPOSE 5174制作前端镜像
docker build -t vue_nginx .2.4.4 更新启动容器的配置文件
来到部署环节当然就是编写 docker-compose.yml 文件了。但多个容器之间涉及到文件的相互交互以及nginx的配置需要细致的记录,以此减少往后项目更新的工作量,这项工作会在其它日志中更为详细的记录,本文只做简单的解释。以下贴出最终版本的 docker-conpose.yml,鉴于 2.3.5 已经对前两个容器做出了解释,本小节只对 vue_nginx 做一些简单的注解。
services:
blog_django:
image: blog_django
container_name: blog_django
volumes:
- /Users/hiko/Documents/BlogData/db_data:/app/database
- /Users/hiko/Documents/BlogData/md_data:/app/markdown
restart: always
environment:
- DJANGO_SETTINGS_MODULE=blogDjango.settings
networks:
- blog-network
django_nginx:
image: nginx:latest
container_name: django_nginx
ports:
- "44400:80"
volumes:
- /Users/hiko/Documents/BlogData/django_data/staticfiles:/staticfiles
- /Users/hiko/Documents/BlogData/django_data/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- blog_django
networks:
- blog-network
vue_nginx:
image: vue_nginx
ports:
- "44401:5174"
volumes:
# /markdown 挂载到了宿主机存储博文文章的地址,作为静态文件被访问,这区别于其它的项目,该逻辑在后续的日志中进行更细致的记录说明
- /Users/hiko/Documents/BlogData/md_data:/markdown
# 将nginx的配置文件挂载出来,方便配置文件的修改
- /Users/hiko/Documents/BlogData/vue_data/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- django_nginx
networks:
- blog-network
networks:
blog-network:vue_nginx 的配置文件:nginx.conf
server {
# 设定服务器监听端口
listen 5174;
listen [::]:5174;
server_name localhost;
# 将博文作为静态文件
location /markdown/ {
alias /markdown/;
}
# 设定 axiox 的访问地址
location /blog/client/ {
proxy_pass http://django_nginx/client/;
proxy_set_header Host $host;
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;
}
# 设定服务器代理(此处将所有路由代理到了demo前端网页)
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}前后端数据与文件的访问逻辑不在本总结中详细叙述。本总结只对关键配置文件进行记录与简单说明。
2.4.5 项目部署
这里贴出本项目数据文件的文件目录以及说明,也就是容器的挂载目录。
— BlogData
|- db_data
|- md_data
|- django_data
| |-staticfiles
|- vue_data所有文件准备完成之后,就开始部署项目。
docker compose -p hikoblog up以上,项目部署完毕。