ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python的校园招聘系统:Django+Flask+Vue3实战

基于Python的校园招聘系统:Django+Flask+Vue3实战 有段时间没更新项目实战类的内容了。前阵子帮一位导师朋友把一套校园招聘系统从零搭起来前后一共花了两周多。现在这个项目已经稳定跑在他们学校的就业信息中心学生投简历、企业发布岗位、管理员审核整条流程都顺了。今天把整个过程整理出来包括技术选型、功能拆解、前后端实现以及我在这两周里反复踩过的坑。项目本身不算复杂但覆盖的知识面很广后端用 Python 写核心业务交给 Django简历解析单独用 Flask 起了一个微服务前端选了 Vue 3 Element Plus开发全流程都在 PyCharm 里完成。这套组合在高校项目、毕业设计和学生团队里都非常常见。如果你正打算用 Python 从零做一个前后端分离的招聘系统或者一直没搞明白 Django 和 Flask 在同一个项目里到底怎么分工这篇可以直接当参考资料用。1. 校园招聘系统的项目背景与整体设计1.1 为什么选校园招聘这个场景校园招聘这个场景的痛点其实非常明显。每年春招秋招那几个月学生要在各个招聘平台、学校就业网、企业官网之间来回切换一个不小心就错过投递窗口企业 HR 的邮箱里塞满了命名混乱的简历文件筛选效率极低学校的就业指导中心又没法及时掌握学生的投递情况和企业反馈。三方都有需求但是中间缺一个能把信息串起来的平台。做一个校园招聘系统的意义就在这里。它要解决的问题不是单纯地把职位信息列出来而是把“职位发布 - 学生浏览 - 简历投递 - 企业筛选 - 面试通知 - 结果反馈”整条链路搬到线上让每一方都能实时看到自己关心数据的状态。这也是这个项目最核心的价值所在所有功能设计都得围着这条主链路转。1.2 系统角色与核心功能拆解我把系统的用户分成三类学生、企业、管理员。每一类角色的需求差异很大功能边界必须一开始就划清楚不然后面接口设计会越写越乱。角色核心功能关键逻辑学生注册登录、完善简历、浏览职位、搜索筛选、投递简历、查看投递反馈一份简历可投多个职位但同一职位不能重复投递企业注册登录、企业认证、发布职位、查看投递简历、发送面试邀请只有审核通过的账号才能发布职位职位需要管理员审核上架管理员学生与企业账号管理、职位审核、数据统计后台操作不开放注册入口这三个角色对应到数据库设计上就是三套独立的业务表。学生和企业其实都是“用户”所以用户表需要扩展角色字段职位表和企业账号关联简历表和学生账号一对一关联投递记录表则把学生、职位、简历三者关联起来。这个关系理清了后面建模型就顺理成章。1.3 总体技术架构与开发思路开发前我先把技术架构定下来了前后端分离Django 提供主业务 APIFlask 提供简历解析微服务Vue 3 负责前端界面MySQL 存数据JWT 做登录认证。开发阶段前端跑在 Vite 开发服务器上通过代理把 /api 请求转发到 Django部署阶段再把 Vue 构建出来的静态文件放到 Nginx由 Nginx 反向代理到后端。为什么选这套架构而不是传统的 Django 模板渲染原因很简单招聘系统的页面交互远比传统多页应用复杂。职位筛选要即时响应简历表单要动态校验投递状态要实时刷新这些需求用 Vue 这种组件化框架实现起来会顺手得多。同时前后端分离之后前端同学和后端同学可以并行开发互相只依赖接口文档开发效率有明显提升。2. 技术选型解析Vue Django Flask 的组合逻辑2.1 Django 与 Flask 在项目里的角色分工标题里同时出现 Django 和 Flask很多读者可能会疑惑这两个框架不是二选一的关系吗怎么能在同一个项目里同时出现其实完全可以而且这种“一主一辅”的组合在实际项目中并不少见。我的分工原则是核心业务交给 Django辅助服务交给 Flask。招聘系统的主业务是用户管理、职位 CRUD、投递管理这种典型的业务系统用 Django 非常稳。Django 自带 ORM、数据库迁移、Admin 后台、表单校验再加上 Django REST Framework 这个强力插件接口开发效率极高。尤其 Admin 后台几乎零成本就解决了管理员的职位审核需求这在 Flask 里得自己写一堆页面。Flask 负责的是“简历解析”和“职位推荐”这类相对独立的功能。学生在 PC 端上传的简历可能是 PDF 也可能是 Word系统需要把文件内容提取出来、抽取出姓名、毕业院校、专业技能这些关键信息。这类逻辑和主业务没有强耦合而且需要引入 pdfplumber、python-docx 这些第三方解析库如果直接塞进 Django 项目里会让主项目的依赖变得臃肿。单独用一个 Flask 服务把这些事情接住既隔离了依赖又不影响主业务的稳定性。选型阶段我也对比过 FastAPI它在性能和自动文档上有优势但当时团队里对 FastAPI 的异步写法不熟悉综合考虑后还是选择了 Flask。技术选型没有绝对的好坏关键是团队里谁更熟、维护成本更低。2.2 前端框架选型为什么选择 Vue 3前端框架当时在 Vue 和 React 之间也犹豫了一下最终选了 Vue 3核心原因是三个中文文档友好、上手曲线平缓、Element Plus 组件库太契合这个项目了。招聘系统的前端界面说白了就是大量列表页、搜索筛选栏、表单弹窗、状态标签。Element Plus 把这些东西全部组件化了一个 select、一个 date-picker、一个表格分页直接引用组件就能用不需要自己去手写复杂交互。相比之下 React 的生态更分散需要自己挑 UI 库和状态管理方案配置成本更高。Vue 3 的组合式 API 也让代码组织更清爽。职位列表页里搜索条件、分页参数、列表数据这些逻辑可以集中在一个 setup 函数里逻辑清晰不容易写着写着就散一地。而且 Vue 的单文件组件结构天然适合“一个页面一个文件夹”的开发模式和我这边的后端接口能够一一对应沟通成本低。如果是对前端不太熟悉的后端开发者Vue 的模板语法比 React 的 JSX 更接近传统 HTML 的书写直觉很多后端同学看两天 Vue 文档就能上手改页面了。这一点在高校团队里尤为重要毕竟不是每个团队都有专职前端。2.3 开发环境与 PyCharm 的工程化配置整个项目我是在 PyCharm 里开发的PyCharm 对这个技术栈的整合做得确实很到位。先说版本选择我个人建议用 Professional 版它对 Django 的支持特别深能直接识别模型字段、模板变量、URL 路由跳转非常方便。学生和教师可以申请 JetBrains 的免费教育授权个人开发者也有社区教育优惠别去琢磨那些乱七八糟的激活方式正规渠道拿到授权很简单。PyCharm 里我做了几个关键配置。首先是项目解释器一定要指向当前项目的虚拟环境venv这样终端里装的包和 IDE 里用的包才是一致的。其次是安装了 Vue.js 插件让 .vue 文件有语法高亮和代码提示。再就是配置了两个运行任务一个跑 Django端口 8000一个跑 Flask端口 5001开发的时候两个服务同时启动直接点 IDE 的绿色按钮就能调试比在命令行里来回切换窗口舒服得多。另外强烈建议配置一下代码风格检查PyCharm 自带的 EditorConfig 和 Python 的代码风格检查工具配合保存文件时自动格式化团队协作时代码风格能保持统一。3. 从零搭建项目环境准备与工程初始化3.1 Python、Node 与 MySQL 环境准备在开始写代码之前先把环境准备好。我用的版本组合如下Python 3.11不用 3.12部分第三方库对 3.12 的兼容性还不完善Node.js 18 LTS npmMySQL 8.0字符集必须设置成 utf8mb4不然存不了特殊字符和 emojiPyCharm 2023.3 及以上版本Python 虚拟环境一定要建好python -m venv venv source venv/bin/activate # Windows 用户执行 venv\Scripts\activate然后在虚拟环境里装后端依赖。说实话这一步我踩过坑一开始图省事直接 pip install django 就完事了结果后面装 mysqlclient 的时候爆了一堆依赖错误。正确的做法是先把项目的基础依赖一次性装全pip install django djangorestframework django-cors-headers PyJWT mysqlclientmysqlclient 在 Windows 上装起来可能有点麻烦装不上就直接用 pymysql然后在 Django 项目的__init__.py里加一行pymysql.install_as_MySQLdb()也能顶上去。这不是最优方案但胜在省事。3.2 Django 后端项目初始化后端项目的初始化非常机械按下面几步走就行django-admin startproject recruitment_backend cd recruitment_backend python manage.py startapp accounts python manage.py startapp jobs python manage.py startapp resumesaccounts 负责用户和认证jobs 负责职位和投递resumes 负责简历管理。拆成三个 app 的核心原因是为了代码结构清晰每个 app 只关心自己的业务域后面维护起来不用在一个大 models.py 里翻几百行。初始化完成后立刻去 settings.py 里做三件事把 INSTALLED_APPS 加上 rest_framework、corsheaders 和三个业务 app把 DATABASES 改成 MySQL 连接配置加上 MEDIA_URL 和 MEDIA_ROOT 配置因为后面简历文件要存到本地磁盘。# settings.py 关键配置 INSTALLED_APPS [ ... rest_framework, corsheaders, accounts, jobs, resumes, ] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: recruitment, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }3.3 Vue 3 前端项目初始化前端我用 Vite 来初始化命令很简单npm create vitelatest recruitment_frontend -- --template vue cd recruitment_frontend npm install npm install element-plus axios vue-router4 piniaelement-plus 负责 UI 组件axios 负责和 Django 交互vue-router 负责页面路由pinia 负责全局状态管理。注意现在 Vue 3 项目基本默认走 Vite 而不是 webpackVite 的启动速度是真的快开发体验比几年前的 vue-cli 好太多了。装完依赖后把 Vite 的开发端口固定一下在 vite.config.js 里配置 server 端口和代理import { defineConfig } from vite; import vue from vitejs/plugin-vue; export default defineConfig({ plugins: [vue()], server: { port: 3000, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } });这样开发时前端访问/api/login就会自动转发到 Django 的http://127.0.0.1:8000/api/login跨域问题在开发阶段就被绕过去了。3.4 项目目录结构与规划思路开发期我把前端和后端放在同一个仓库下用两个二级目录区分结构如下recruitment/ ├── backend/ # Django 项目 │ ├── manage.py │ ├── recruitment_backend/ │ ├── accounts/ │ ├── jobs/ │ └── resumes/ ├── frontend/ # Vue 3 项目 │ ├── src/ │ ├── vite.config.js │ └── package.json └── scripts/ # 部署脚本开发前期放同一个仓库方便联调git 也只管这一个仓库就行。部署的时候再分开处理Django 代码部署到服务器Vue 构建后的静态文件交给 Nginx互不干扰。这个“开发合一、部署分离”的思路在很多学生项目里都适用省心。4. 后端核心实现用户体系与数据模型设计4.1 用户模型扩展与角色设计Django 自带的用户模型在简单场景里够用但招聘系统需要区分学生、企业和管理员三类角色所以必须继承 AbstractUser 扩展字段。这是 Django 官方推荐的做法比另建一张用户资料表去关联要优雅得多。# accounts/models.py from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES ( (student, 学生), (company, 企业), (admin, 管理员), ) role models.CharField(max_length20, choicesROLE_CHOICES, defaultstudent) phone models.CharField(max_length20, nullTrue, blankTrue) avatar models.ImageField(upload_toavatar/%Y%m%d/, nullTrue, blankTrue) company_name models.CharField(max_length100, nullTrue, blankTrue) company_credit_code models.CharField(max_length50, nullTrue, blankTrue) is_verified models.BooleanField(defaultFalse)这里说一个实际开发中的细节企业用户注册后不能立刻发布职位要有一个管理员审核的环节所以我加了 is_verified 字段。学生注册则不需要审核默认就能使用系统。这个逻辑不只是加一个字段的问题还直接影响了后面接口的权限控制。4.2 职位、简历与投递业务模型职位表是招聘系统的核心业务表字段比较多。我设计的要点是薪资用两个整数字段而不是一个字符串因为后面做筛选排序的时候字符串解析会非常痛苦。# jobs/models.py class Job(models.Model): STATUS_CHOICES ( (0, 草稿), (1, 审核中), (2, 已上架), (3, 已下架), ) company models.ForeignKey(User, on_deletemodels.CASCADE, related_namejobs) title models.CharField(max_length100) category models.CharField(max_length50) salary_min models.IntegerField(default0) salary_max models.IntegerField(default0) city models.CharField(max_length50) description models.TextField() requirement models.TextField() status models.IntegerField(choicesSTATUS_CHOICES, default1) view_count models.IntegerField(default0) created_time models.DateTimeField(auto_now_addTrue) class Application(models.Model): STATUS_CHOICES ( (1, 待查看), (2, 已查看), (3, 已通过), (4, 已拒绝), ) job models.ForeignKey(Job, on_deletemodels.CASCADE, related_nameapplications) student models.ForeignKey(User, on_deletemodels.CASCADE, related_nameapplications) resume models.ForeignKey(resumes.Resume, on_deletemodels.CASCADE) status models.IntegerField(choicesSTATUS_CHOICES, default1) created_time models.DateTimeField(auto_now_addTrue) class Meta: unique_together (job, student)这里有两个关键点。第一职位和用户外键关联时用 related_name 避免反向查询冲突第二Application 表设置了 unique_together从数据库层面保证同一个学生不能重复投递同一个职位。你要是只在前端做判断并发请求下很容易出现脏数据数据库约束才是最终的兜底。简历表的模型相对简单和学生是一对一关系# resumes/models.py class Resume(models.Model): student models.OneToOneField(User, on_deletemodels.CASCADE, related_nameresume) name models.CharField(max_length50) phone models.CharField(max_length20) email models.EmailField() education models.CharField(max_length100) major models.CharField(max_length100) skill_tags models.CharField(max_length200, blankTrue) self_evaluation models.TextField(blankTrue) attachment models.FileField(upload_toresume/%Y%m%d/, nullTrue, blankTrue) updated_time models.DateTimeField(auto_nowTrue)4.3 DRF 接口设计与 JWT 认证接口层我用 Django REST Framework 的 APIView 来实现。没有一上来就上 ViewSet因为招聘系统的接口虽然多但每个接口的业务逻辑都不太一样APIView 写出来更直白也更容易让团队新人理解。JWT 认证是标配。安装配置如下# settings.py from datetime import timedelta SIMPLE_JWT { ACCESS_TOKEN_LIFETIME: timedelta(days1), REFRESH_TOKEN_LIFETIME: timedelta(days7), } REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticated, ], }核心接口清单如下方法路径说明权限要求POST/api/auth/register/注册匿名可访问POST/api/auth/login/登录获取 Token匿名可访问GET/api/jobs/职位列表含搜索筛选登录用户GET/api/jobs/{id}/职位详情登录用户POST/api/jobs/{id}/applications/投递简历学生角色GET/api/applications/mine/我的投递记录学生角色POST/api/resumes/创建/更新简历学生角色GET/api/company/jobs/企业端职位管理企业角色写一个典型的企业发布职位接口做示例# jobs/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from .models import Job from .serializers import JobSerializer class CompanyJobCreateView(APIView): permission_classes [IsAuthenticated] def post(self, request): if request.user.role ! company: return Response({detail: 无权限}, status403) data request.data.copy() data[company] request.user.id serializer JobSerializer(datadata) if serializer.is_valid(): serializer.save() return Response(serializer.data, status201) return Response(serializer.errors, status400)注意到关键的细节了吗创建职位的时候company 字段不从客户端传而是从 request.user 里取。这样能防止一个企业用户伪造数据替其他企业发职位是后端的权限校验兜底。5. Flask 微服务拆解简历解析与智能推荐5.1 为什么额外部署一个 Flask 服务简历解析这个功能是学生在线上传 PDF/Word 简历后系统自动提取文本内容、抓取姓名、教育经历、技能关键词然后回填到简历表单。这个需求和主业务没有强关联完全可以独立出去单独跑。之所以用 Flask 而不是直接在 Django 里写有几个实际考虑。一是依赖隔离pdfplumber 处理 PDF 时会牵连一些底层编译包没必要污染主项目的环境二是故障隔离在线简历解析偶尔会因文件格式奇特而报错独立成服务后即使挂了也不影响核心的职位浏览和投递功能三是扩展方便以后如果要做批量解析、智能推荐这个 Flask 服务可以单独扩容横向加实例。5.2 简历文件解析服务的实现这个 Flask 服务我觉得是整套系统里最具“微服务味道”的部分。核心代码大致如下# resume_parser/app.py from flask import Flask, request, jsonify import pdfplumber import docx app Flask(__name__) KEYWORD_DICT [python, java, vue, django, flask, mysql, linux] app.route(/parse/resume, methods[POST]) def parse_resume(): file request.files.get(file) if not file: return jsonify({error: no file}), 400 filename file.filename or text if filename.endswith(.pdf): with pdfplumber.open(file) as pdf: text \n.join(page.extract_text() or for page in pdf.pages) elif filename.endswith(.docx): doc docx.Document(file) text \n.join(paragraph.text for paragraph in doc.paragraphs) else: return jsonify({error: unsupported format}), 400 # 提取出关键词供推荐模块使用 matched_keywords [kw for kw in KEYWORD_DICT if kw.lower() in text.lower()] return jsonify({ filename: filename, text_length: len(text), keywords: matched_keywords, }), 200 if __name__ __main__: app.run(host127.0.0.1, port5001)实际开发中用 pdfplumber 提取 PDF 文本时经常会遇到扫描件或者加密 PDF处理起来会复杂很多但作为第一版能解析常见的文字型 PDF 和 Word 已经足够用了。注意 Word 有 .doc 和 .docx 两种格式老的 .doc 文件用 python-docx 处理不了得先调用系统命令转成 .docx这个注意点写在代码注释里后来接手的同学才没被坑。5.3 Django 与 Flask 服务如何通信Django 这边通过 requests 库去调用 Flask 服务# resumes/services.py import requests def call_flask_parse(file_bytes, filename): try: resp requests.post( http://127.0.0.1:5001/parse/resume, files{file: (filename, file_bytes)}, timeout10 ) resp.raise_for_status() return resp.json() except requests.RequestException as exc: # 微服务不可用时降级处理不让用户上传简历直接报错 return {error: parser service unavailable, detail: str(exc)}这里有一个工程上很关键的设计Flask 服务不可用时Django 采取“降级策略”即简历仍然可以上传到本地保存只是不解析文本内容用户之后可以手动填写。核心链路不能因为辅助服务挂掉而中断这是做微服务架构一定要记住的原则。6. Vue 3 前端核心页面与交互实现6.1 前端路由设计与登录守卫前端用 vue-router 4 做路由管理我把所有页面按角色划分在路由配置里加了元信息来控制访问权限const routes [ { path: /, redirect: /jobs }, { path: /login, component: () import(./views/LoginView.vue) }, { path: /register, component: () import(./views/RegisterView.vue) }, { path: /jobs, component: () import(./views/JobListView.vue) }, { path: /jobs/:id, component: () import(./views/JobDetailView.vue) }, { path: /resume, component: () import(./views/ResumeEditView.vue), meta: { requiresAuth: true } }, { path: /applications, component: () import(./views/ApplicationListView.vue), meta: { requiresAuth: true } }, { path: /company/jobs, component: () import(./views/CompanyJobsView.vue), meta: { requiresAuth: true, roles: [company] } }, ];登录守卫的逻辑是在 router.beforeEach 里判断router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); return; } if (to.meta.roles) { const role localStorage.getItem(user_role); if (!to.meta.roles.includes(role)) { next(/jobs); return; } } next(); });守卫的逻辑写在路由层面页面组件内部就不用每个都去检查有没有登录了。这个模式在中小型后台管理系统里特别实用但需要注意前端的角色控制只是用户体验层面的真正的权限校验必须在后端做前端跳转说白了只是关了个门后端的接口权限才是一堵真正的墙。6.2 Axios 封装与 Token 自动携带前端所有 HTTP 请求都通过一个统一的 Axios 实例来发避免每个组件里都写一遍请求配置// src/utils/http.js import axios from axios; const http axios.create({ baseURL: /api, timeout: 10000, }); http.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); http.interceptors.response.use( response response, error { if (error.response error.response.status 401) { localStorage.removeItem(token); window.location.href /login; } return Promise.reject(error); } ); export default http;请求拦截器自动把 Token 塞进 Authorization 头响应拦截器统一处理 401 跳转。这个封装一次性解决了登录态维护的大半问题。要注意的是Token 过期后不能只跳转还要把本地缓存的用户信息清掉不然后端没数据、前端还以为自己登录着会出现各种诡异状态。6.3 职位浏览、投递与状态跟踪职位列表页是这个系统使用频率最高的页面交互上由三部分组成搜索栏、筛选条件、列表卡片。我在前端用了 Element Plus 的 el-form 做搜索条件el-card 展示职位卡片分页交给 el-pagination。职位详情页的核心是“立即投递”按钮。点击后先检查用户是否已经填写过简历没有的话跳转到简历编辑页并给出提示有简历的话直接调用投递接口。为了防止用户重复投递后端有 unique_together 兜底前端也做了友好提示async function applyJob(jobId) { try { await http.post(/jobs/${jobId}/applications/); ElMessage.success(投递成功); } catch (error) { if (error.response error.response.status 400) { ElMessage.warning(你已经投递过这个职位了); } } }学生用户在自己的“投递记录”页面用 el-tag 展示不同状态的申请记录。这里的状态值前后端必须约定好我后端用的是 1待查看、2已查看、3已通过、4已拒绝前端做了一一映射。前端不要直接用数字显示给用户这种硬编码的显示逻辑是很多项目后期改款的痛。企业端的职位管理页面就完全不一样了用的是表格布局每一行展示一个职位操作列里有“编辑”“下架”“查看简历”三个按钮。查看简历时弹窗里展示的是该职位的所有投递记录每条记录关联一个学生简历的摘要信息企业可以直接标记“通过”或“拒绝”。到这里整条招聘链路就闭环了。7. 前后端联调部署与高频问题排查7.1 跨域与代理配置解决联调第一关开发期前后端分离最大的拦路虎就是跨域。虽然我在 Vite 配置里加了 proxy把 /api 请求代理到了 Django但还是遇到过前端请求报 CORS 的情况。排查下来发现是前端有时直接访问了http://127.0.0.1:8000而不是通过 Vite 代理绕过了代理自然就触发了浏览器同源策略。为了保险起见Django 这边也配置了 django-cors-headers# settings.py INSTALLED_APPS [... corsheaders, ...] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:3000, http://127.0.0.1:3000, ]联调时的建议是前端统一走/api相对路径不要写死完整域名这样不管是本地开发还是部署到服务器都可以通过反向代理解决跨域代码不用改。7.2 高频问题与解决方案速查表这是我在开发过程中切切实实遇到过、而且问朋友也经常被问到的几个高频问题。整理成速查表遇到直接对照解决问题现象解决思路图片或简历上传后访问不到文件保存成功但 URL 打开 404检查 settings.py 的 MEDIA_URL 和 Django 静态文件服务配置开发期要在 urls.py 手动加 static 路由JWT 登录后接口仍然 401前端报 401 Unauthorized检查 axios 拦截器是否带上了 Authorization 头Token 是否过期Token 过期后要用 refresh token 刷新数据库中文乱码存入 MySQL 的中文显示问号建库时指定 utf8mb4 字符集排序规则选 utf8mb4_unicode_ciFlask 服务解析耗时太长前端请求转圈 10 多秒才返回大文件场景下把同步解析改成异步任务前端先返回“解析中”Element Plus 中文语言包不生效组件内置文案是英文在入口文件引入 zhCn locale 配置Python 版本兼容问题某些库安装报错统一用 Python 3.11requirements.txt 锁住版本7.3 部署要点与工程化经验我把前后端同时部署到一台轻量云服务器上。Django 用 Gunicorn 跑Flask 单独用另一个端口跑前端 Vue 构建出静态文件后放到 Nginx 的 html 目录Nginx 配置两个 location一个指向静态文件一个把 /api 反向代理给 Gunicorn。工程化上面有几点经验特别想分享。第一依赖一定要锁版本ner requirements.txt 里写死版本号别写那种大于号不带上限的宽松版本不然半年后再部署会发现环境根本跑不起来。第二所有接口的返回格式要统一我用的格式是{ code: 200, message: success, data: {...} }前端处理逻辑会简单很多。第三日志要分级输出开发开 DEBUG生产一定关掉避免暴露数据库信息。7.4 数据库迁移与版本回滚注意事项这个点容易被忽略但实际项目里非常容易出事。Django 的 migrate 命令生成了很多迁移文件团队协作时如果两个人同时改了同一个模型迁移文件就会冲突一执行 migrate 就报错依赖混乱。我的建议是模型字段的修改频率要控制设计阶段就把 90% 的字段定下来上线后不要频繁加字段。如果确实要改每次改动对应一个专门的迁移文件并且把迁移文件提交到 git。生产环境执行迁移前一定要备份数据库万一回滚出问题不至于丢数据。我在这个项目里就因为一次不规范迁移折腾了整整一晚上数据库表结构乱了最后靠备份恢复才解脱。8. 写在最后的几点体会做这个校园招聘系统除了技术上的成长我更深的体会来自架构思维。你看这个项目Django 不是唯一选项Flask 也不是为了炫技加进去的它们各自承担了最合适的职责模块中间用 HTTP 接口连接本质上已经是一个微服务的雏形了。对于学生项目来说这种“不盲目上复杂架构但也不把所有代码揉成一坨”的平衡感其实比堆一堆技术名词更值钱。再分享一个小技巧开发这个系统时我用 PyCharm 的 REST Client或者说直接手写接口测试脚本把核心接口全部过了一遍每个接口写了一个简单的测试用例。后面前端联调时很多问题五分钟内就能定位是后端还是前端的锅效率拉满。如果你是自己一个人从零做整套系统强烈建议在写前端之前先把后端的接口全部用接口测试工具跑通一遍再开始接页面。这套系统后续还可以往两个方向扩展一个是加一个在线笔试模块企业创建笔试任务学生在规定时间内完成编程题另一个是做一个基于大模型的简历智能评估把 Flask 服务升级一下让算法自动匹配岗位要求给学生打分。方向不复杂但每一次扩展都别忘了回到最开始那条主链路去问自己这个功能到底是在帮学生更快找到工作还是帮企业更快招到人。答案明确了技术怎么做都是顺手的事。
返回列表