拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于Django与DRF的人事管理系统开发与毕设实践

基于Django与DRF的人事管理系统开发与毕设实践

1. 项目概述与适用人群

先交代一下背景:这份基于Python的人事管理系统毕设源码,技术栈是Django + DRF(Django REST Framework)后端,前端可选择传统模板渲染或Vue管理后台,属于典型的信息管理系统类毕业设计选题。人事管理系统在国内计算机相关专业的毕设选题中一直居高不下,因为它功能边界清晰、模块划分自然、涉及的角色和权限关系完整,而且容易产出可视化的演示效果。无论你是还没定题的学生,还是已经拿到源码但跑不起来、讲不清楚的在写论文阶段的人,这套系统的设计思路和排坑经验都有参考价值。

单从题目看,"设计与实现"这个表述已经锁定了项目的两个核心交付物:一是能跑的程序,二是能通过答辩的文档。程序部分要解决的痛点是代码能不能在评委面前顺利运行,文档部分要解决的痛点是论文里有没有足够的系统分析、数据库设计、模块详设来支撑篇幅。所以我不打算只讲怎么敲代码,我会把从环境搭建、数据表设计、接口实现到答辩讲解的一整条链路拆开,把容易卡住的地方都挑明。前期准备做得越细,后期填坑越少,这个道理在毕设场景里比任何技术选型都重要。

这套源码整体设计上遵循了一个很务实的逻辑:用Django自带的后台体系和DRF的视图集快速支撑起员工、考勤、薪资、请假、部门、系统管理六个核心模块,既保证了功能完整性,又控制了代码量。对于需要一个月内完成毕设、还要留出时间写论文的同学来说,这种"框架优先、模块化组装"的思路是最稳的。

2. 环境准备与项目骨架搭建

2.1 Python版本与Django版本的选择

Django项目跑不起来,绝大多数情况不是代码写错了,而是环境版本不匹配。人事管理系统这类后端项目,我建议直接用Python 3.10或3.11,Django选4.2 LTS版本。为什么要锁版本?因为很多毕设源码是从老项目改过来的,如果requirements.txt写的是Django==2.2,你在Python 3.11下跑就会遇到pymysql连接报错、uWSGI编译失败之类的连锁问题。

环境搭建的完整步骤大致是这样:安装Python后,在项目根目录创建虚拟环境python -m venv venv,激活虚拟环境后执行pip install -r requirements.txt。如果requirements.txt缺失或没锁版本,就手动安装基础搭配:Django==4.2.11、djangorestframework==3.15.1、corsheaders、pymysql、openpyxl。这里有个细节:如果你用的是Windows系统,激活虚拟环境的命令是venv\Scripts\activate,macOS和Linux是source venv/bin/activate。很多同学卡在这一步,然后误判成代码问题,实际上只是虚拟环境没激活就直接执行了python manage.py runserver,调用了全局Python环境,自然找不到依赖。

2.2 配置文件的骨架:settings.py里的关键改动

新建Django项目之后,第一步要做的就是改settings.py。默认的settings直接跑是可以的,但要让人事管理系统支撑起后续模块化开发,有几项必须提前配置:

# settings.py 核心配置片段 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'corsheaders', 'apps.employee', 'apps.attendance', 'apps.salary', 'apps.leave', 'apps.system', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # ... Django默认中间件 ] # 自定义用户模型 AUTH_USER_MODEL = 'system.User' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'hrms', 'USER': 'root', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', }, 'CONN_MAX_AGE': 60, } } REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, }

这里有一个必须强调的坑:自定义用户模型一定要在第一次迁移之前配置好。AUTH_USER_MODEL = 'system.User'这行代码如果你是在数据库已经有auth_user表之后才加的,迁移时会报InconsistentMigrationHistory,轻则卡住migrate命令,重则只能删库重来。人事系统天然需要扩展用户字段,比如工号、角色、所属部门,所以我一开始就把用户模型定义在system这个app里,不走Django默认的auth.User。

数据库选择上,演示项目用SQLite最方便,但如果你计划在论文中写"系统基于MySQL数据库设计",那么环境里就要提前装好MySQL 5.7或8.0,并创建好名为hrms的数据库。SQLite和MySQL在Django里的切换成本很低,改一下DATABASES配置即可,但要注意MySQL版本和pymysql的兼容性。我的实践是用MySQL 8.0.x +pymysql,初始化时需要在项目的__init__.py里加上pymysql.install_as_MySQLdb()。

2.3 数据表设计:六个核心模块的拆分逻辑

人事管理系统的核心在于数据模型设计。我一贯的拆分逻辑是:员工、部门、岗位、考勤、薪资、请假六个模块,外加系统用户管理。每个模块的表不要贪多,控制在1-3张表,既能撑起功能演示,又不至于让论文里的ER图复杂到画不完。

员工信息表是核心中的核心,字段设计上除了基础信息,我强烈建议加上employee_no(工号)和status(在职状态)这两个字段。工号必须唯一,后续考勤、薪资都靠它做关联;在职状态有active、leave、resigned三种选择,列表筛选时非常有用。部门表和岗位表用外键关联到员工表,on_delete参数我用的是PROTECT而不是CASCADE,目的是防止部门被删时把员工数据连带删除,真实业务中这种静默丢数据是不可接受的。

考勤表的设计要特别注意约束。同一员工同一天只能有一条考勤记录,所以Attendance模型的Meta里要配置unique_together = ('employee', 'date')。打卡时间用TimeField可空字段,早退、迟到这些状态可以单独加一个字段来描述,但不要用布尔值,因为状态种类会越来越多,用choices字符串枚举更合理。

薪资模块我拆分成了SalaryStandard和SalaryRecord两张表。标准表保存基本工资、岗位工资、绩效基数的设定;记录表保存某个员工某个月实际发放的数额和明细。这个拆分是必要的,因为薪资标准会调整,如果只在员工表里存一个工资字段,历史数据就全乱套了。请假模块的LeaveRequest相对简单,核心是状态流转:待审批、已通过、已驳回,再加上请假类型、开始时间、结束时间、请假事由。

系统用户表放在system模块,字段除了继承AbstractUser,还加了role(角色)字段,取值是admin、hr、manager、employee四种。这样在做权限控制时,直接判断request.user.role就能区分操作权限,比维护一张复杂的权限关联表省事得多,也足够应对毕设答辩中"权限管理是怎么设计的"这个问题。

2.4 初始化数据与Fixtures

Django的python manage.py migrate只会建表,不会帮你填充数据。人事系统如果空着表演示,效果非常差。我的习惯是准备一个fixtures/initial_data.json,里面放部门、岗位、管理员账号、测试员工记录等基础数据。答辩前执行python manage.py loaddata initial_data,系统瞬间就有了一套完整的演示数据。这个操作可以在论文的"系统测试"部分作为初始化数据准备写入,也能在答辩现场节约大量录入时间。

3. 核心功能模块与DRF接口设计

3.1 模块划分与技术选型:前后端分离还是不分离

人事管理系统有两种主流的落地形态:一种是Django模板 + Bootstrap/Layui的传统后端渲染页面,前端逻辑简单,适合一个人快速开发;另一种是Django + DRF提供API,前端用Vue或React的管理后台模板。我要明确说的是,两种方案都能做出合格的人事管理系统,但它们的代码组织方式完全不同。

如果你选择传统模板渲染,核心代码集中在views.py里,每个功能对应一个函数视图,渲染的HTML模板去Django模板语法写循环和判断。这种方案的优点是技术栈简单、不需要考虑跨域问题、部署时不用单独跑前端项目的打包流程;缺点是人机交互差一些,做复杂筛选和弹窗表单时前端代码会变得很啰嗦。

如果你选择前后端分离,后端就是纯粹提供JSON接口,前端页面用Vue + Element Plus这类现成组件库来拼。这种方案的优点是界面美观、交互流畅、在答辩演示时视觉效果好;缺点是项目体积变大,需要同时维护前后端两套代码,部署时要么把前端build后的静态文件交给Django托管,要么分开部署用Nginx代理。

我最终选择的是前后端分离。原因是人事管理系统管理后台的前端模式高度雷同,无非是表格列表、搜索筛选、弹窗表单、树形选择,用组件库开发效率非常高。后端用DRF提供的ModelViewSet,绝大部分增删改查接口不需要手写,路由注册也是自动化的。这套组合是毕设系统里性价比最高的选择。

3.2 ModelViewSet的使用与扩展

DRF的ModelViewSet是个双刃剑。好处是注册一个路由就自动获得list、create、retrieve、update、destroy五个接口;坏处是真实业务逻辑根本不会刚好匹配默认行为,一定会需要扩展或覆盖。

拿员工列表接口举例,默认的list返回的是全表数据,但实际场景中肯定要支持按姓名搜索、按部门筛选、按状态过滤。我的做法是重写get_queryset方法:

from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response class EmployeeViewSet(viewsets.ModelViewSet): queryset = Employee.objects.all() serializer_class = EmployeeSerializer def get_queryset(self): queryset = super().get_queryset() name = self.request.query_params.get('name') department_id = self.request.query_params.get('department_id') status = self.request.query_params.get('status') if name: queryset = queryset.filter(name__icontains=name) if department_id: queryset = queryset.filter(department_id=department_id) if status: queryset = queryset.filter(status=status) return queryset.select_related('department', 'position').order_by('-created_at')

这里有一个非常重要的细节:select_related('department', 'position')。Employee表外键关联了部门和岗位,如果查询时不做预取,Django ORM默认在每行数据访问外键字段时都单独发一条SQL。列表返回20条员工记录,就会额外产生40条SQL查询,接口响应时间肉眼可见地变慢。加了select_related之后,Django会通过LEFT JOIN一次性查出关联数据,SQL数量从1+N降为1条。这个优化虽然不起眼,但在答辩演示时如果有评委问"系统性能怎么样",这就是一个很扎实的回答点。

自定义action方面,我一直在用@action装饰器给ViewSet增加额外接口。比如考勤模块需要"按月统计"接口,请假模块需要"审批通过/驳回"接口,这些都不是标准CRUD,但也不需要另起一个ViewSet。用一个@action(detail=False, methods=['get'])包装一下就行:

@action(detail=False, methods=['get']) def monthly_stats(self, request): year = request.query_params.get('year') month = request.query_params.get('month') records = Attendance.objects.filter(date__year=year, date__month=month) # 统计出勤、迟到、请假数据 return Response({...})

3.3 序列化器设计:敏感字段处理与外键嵌套

序列化器是DRF项目里最容易被新手写坏的组件。很多人直接把模型字段一股脑塞进fields = '__all__',结果接口把不该暴露的数据全暴露了。人事系统里,员工表有身份证号、手机号、家庭住址,薪资数据是敏感信息,这些字段绝对不能通过普通员工接口返回。

我的做法是设计两层序列化器:EmployeeListSerializer用于列表展示,只返回工号、姓名、部门、岗位、入职日期等基本字段;EmployeeDetailSerializer用于详情页和管理端操作,包含所有字段。在ViewSet里通过get_serializer_class方法来切换:

def get_serializer_class(self): if self.action == 'list': return EmployeeListSerializer if self.action == 'retrieve': user = self.request.user if user.role in ('admin', 'hr'): return EmployeeDetailSerializer return EmployeePublicSerializer return EmployeeDetailSerializer

这个逻辑同时解决了两个问题:列表页只需要轻量字段,减少数据传输量;详情页根据当前登录用户的角色决定是否展示身份证、工资等隐私信息。权限判断不只在视图层做,序列化输出层也要做,这是很多新手容易漏掉的一环。

外键字段的序列化输出也要单独处理。默认的PrimaryKeyRelatedField只返回一个ID,前端拿到部门ID还得再查一次接口才能显示部门名。我习惯写一个嵌套输出:

class EmployeeListSerializer(serializers.ModelSerializer): department_name = serializers.CharField(source='department.name', read_only=True) position_name = serializers.CharField(source='position.name', read_only=True) class Meta: model = Employee fields = ['id', 'employee_no', 'name', 'department_name', 'position_name', 'hire_date', 'status']

这样接口返回的数据结构对前端非常友好,名字直接带出来了,不需要二次请求。

3.4 权限控制:从登录认证到角色细粒度授权

人事系统的权限设计是答辩评委必问的模块,日常开发中这个点也最容易暴露设计缺陷。我先说完整的权限控制思路,分三层:

第一层是身份认证,确认"你是谁"。我用的是rest_framework_simplejwt,登录接口返回access token和refresh token,前端请求时在Header里带Authorization: Bearer <token>。这套方案和Session的区别在于它无状态,前端哪怕不分离架构也能用,而且默认配置开箱即用。需要改的地方主要是SIMPLE_JWT配置里把ACCESS_TOKEN_LIFETIME调短一点,演示时设成60分钟足够。

第二层是接口权限,确认"你能不能进这个接口"。DRF默认的IsAuthenticated只判断登录状态,但人事系统里薪资接口、请假审批接口、员工删除接口都需要更细的限制。我写了一个自定义权限类:

from rest_framework.permissions import BasePermission class IsHRManager(BasePermission): """HR管理员权限:可以操作员工增删改、薪资管理""" def has_permission(self, request, view): return request.user.is_authenticated and request.user.role in ('admin', 'hr') class IsDepartmentManager(BasePermission): """部门主管权限:可审批请假,不可管理薪资""" def has_permission(self, request, view): return request.user.is_authenticated and request.user.role in ('admin', 'hr', 'manager')

在ViewSet里通过get_permissions动态切换:

def get_permissions(self): if self.action in ['create', 'update', 'partial_update', 'destroy']: return [IsHRManager()] return [IsAuthenticated()]

这里有个小技巧:不要直接在permission_classes属性里写死,因为不同action需要不同的权限策略。写get_permissions方法,按self.action区分,灵活性高得多。

第三层是数据权限,确认"你能看到哪些数据"。普通员工登录系统,应该只能查看自己的考勤、薪资、请假记录,不能看到全公司的数据。这个我用一个简单粗暴的策略:在get_queryset里判断角色,非HR角色就强制加filter(employee__user=request.user):

def get_queryset(self): user = self.request.user if user.role in ('admin', 'hr'): return Attendance.objects.all() return Attendance.objects.filter(employee__user=user)

虽然返回类型不同,但接口契约不变,前端无需改代码。这个"三层权限"的设计思路写进论文,比翻来覆去只说"登录后才能访问"有说服力得多。

3.5 考勤打卡与请假审批:状态机的落地写法

考勤模块除了打卡记录,还需要"手动补卡"、"月度汇总"这类操作。请假模块则需要一个审批状态机。这两个模块展示业务逻辑的能力很强,答辩时值得仔细讲解。

请假的审批流程我用最简单的状态机实现:待审批pending、已通过approved、已驳回rejected。员工提交请假申请后,状态默认pending,只有manager或hr角色能执行审批动作。

@action(detail=True, methods=['post']) def approve(self, request, pk=None): leave = self.get_object() if leave.status != 'pending': return Response({'error': '该申请已处理'}, status=400) if request.user.role not in ('admin', 'hr', 'manager'): return Response({'error': '无审批权限'}, status=403) leave.status = 'approved' leave.approved_by = request.user leave.approved_at = timezone.now() leave.save() return Response({'message': '审批通过'})

驳回逻辑类似,再写一个rejectaction。这个设计的好处是状态只有单向流转,不会出现回退到待审批这种诡异的操作。考勤汇总功能我放在@action(detail=False)里,用date__year和date__month做筛选,配合annotate统计出勤率,返回给前端展示成表格或者图表都很方便。

4. 前端页面与业务闭环

4.1 前端方案选择:Template 渲染还是 Vue 管理后台

如果你是纯后端Django方向,对前端不太熟,选传统模板渲染更快;如果你打算用Vue拼一个现代管理后台,代码工程量会大不少,但演示效果和可扩展性更好。这套源码里我同时保留了两套入口:传统Django模板部分用于快速演示核心页面,Vue管理后台部分用于完整功能展示。

传统模板渲染下,Django模板语言的{% for %}循环和{% if %}判断应付表格展示、状态标签着色这些场景绰绰有余。配合第三方后端模板Layui或AdminLTE,下拉菜单、日期选择器、模态框这些组件开箱即用,不需要前端工程化配置。缺点是写复杂交互时需要大量HTML片段拼接,代码会显得比较乱。

Vue管理后台的目录结构和工程化写法和Django项目不在同一个生命周期里,开发时需要用npm run dev跑本地开发服务器,把API代理到Django的8000端口。部署时执行npm run build,把dist/目录里的静态文件复制到Django的static/目录下,由Django统一托管。这个流程毕设演示完全够用。

4.2 核心页面的功能交互拆解

人事管理系统的前端页面按功能闭环,至少需要这几个页面:

  • 登录页:账号密码登录,登录成功存Token到localStorage
  • 工作台/首页:展示员工总数、部门数、当月待审批申请数量等统计卡片
  • 员工管理页:表格展示员工列表,支持姓名搜索、部门筛选、在职状态筛选;新增/编辑弹窗表单;删除确认框
  • 部门管理页:树形列表展示部门层级,可新增子部门、修改、删除
  • 考勤管理页:按日期范围展示考勤记录,支持补卡操作和月度统计视图
  • 薪资管理页:维护各岗位薪资标准,按月份生成薪资发放记录,支持导出Excel
  • 请假审批页:列表展示待审批申请,点击通过/驳回按钮

页面之间要联动的地方很多,比如员工管理页的部门筛选项,应该由部门管理接口动态获取,不能写死下拉框的值。前端表格的每一行都会操作按钮,行内数据要带上主键ID,这样删除、编辑时才能定位到具体记录。这几个页面的交互虽然简单,但把它们串起来讲一遍,就是完整的需求分析素材。

4.3 前端调用API的封装方式

Vue前端调用Django接口,我会统一封装一个axios实例,baseURL指向/api,请求拦截器里自动带上Token:

import axios from 'axios' const api = axios.create({ baseURL: '/api', timeout: 10000, }) api.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) api.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('access_token') window.location.href = '/login' } return Promise.reject(error) } )

把请求封装成一个api.js文件,页面里直接调用api.get('/employees/')、api.post('/employees/', data),代码复用率高,也方便统一处理错误。需要特别注意的是,如果Vue项目配置了路由模式为history,Django后端要加一个兜底视图,把非/api/路径的请求都引到Vue的入口页面,否则刷新页面会404。

5. 数据库迁移与高频报错排查

5.1 迁移顺序与初始数据准备

对于一个全新的数据库,正常的迁移流程是:配置好settings.py和模型代码后,执行python manage.py makemigrations生成迁移文件,再执行python manage.py migrate应用迁移。如果在makemigrations之前就手动在数据库里建了同名的表,迁移会报Table already exists,这种情况要删除手动建的表,让Django管理表结构。

人事系统的初始数据可以放在fixtures里。这些数据包括超级管理员账号(username为admin,密码自定义)、部门字典(技术部、产品部、人事部等)、岗位字典(开发工程师、产品经理、HR专员等)、以及几条员工和考勤的样例数据。答辩前执行loaddata,系统就能开箱用了,评委看到的数据会比空表丰富得多,这是演示效果提升最大的杠杆。

5.2 高频报错排查:三个我最常被问到的问题

在我的实际经验里,毕设源码交付后收到最多的提问永远是环境类问题。这里把三个高频报错和定位方式写清楚:

问题一:No module named 'rest_framework'

原因几乎只有一种:虚拟环境没装djangorestframework,或者项目用了venv但你用的是全局Python。排查步骤是:确认当前终端是否处于虚拟环境激活状态,执行pip list | grep rest看依赖清单,没有就pip install djangorestframework。如果还不行,检查settings.py的INSTALLED_APPS是否遗漏了'rest_framework'。

问题二:django.db.migrations.exceptions.InconsistentMigrationHistory

这个报错最典型的原因是先跑了migrate,之后才加自定义AUTH_USER_MODEL,导致auth相关迁移和自定义用户模型迁移冲突。解决办法:如果项目刚起步,直接删除旧数据库重建,重新makemigrations和migrate;数据库里有已录入的数据,需要手动处理迁移依赖顺序,实操上最省力的方式是重置迁移文件再--fake-initial。演示环境建议直接重建库,省时省心。

问题三:ImportError: cannot import name 'url' from 'django.conf.urls'

这是Django 4.0+移除了django.conf.urls.url导致的。旧代码中大量使用from django.conf.urls import url来写正则路由,新版彻底删除后报错。解决办法:把url(r'^$', view)改为re_path(r'^$', view),或直接使用path()。如果你拿到的是老源码,批量把url(替换成re_path(就行,注意re_path要从django.urls导入。

5.3 Excel批量导入员工:从txt到表格的实操

人事系统中的批量导入功能看似简单,实际涉及文件解析、数据校验、事务处理三个环节,这个功能在答辩时是加分项。我用openpyxl做Excel解析,核心逻辑是:读取上传文件,按模板格式逐行校验,用事务包裹批量插入,最后返回成功条数和错误明细。

from openpyxl import load_workbook from django.db import transaction @transaction.atomic def import_employees(file): wb = load_workbook(file) ws = wb.active success, errors = 0, [] for row_index, row in enumerate(ws.iter_rows(min_row=2, values_only=True)): employee_no, name, department_name, position_name = row[0:4] department = Department.objects.filter(name=department_name).first() if not department: errors.append(f'第{row_index + 1}行:部门 {department_name} 不存在') continue if Employee.objects.filter(employee_no=employee_no).exists(): errors.append(f'第{row_index + 1}行:工号 {employee_no} 已存在') continue Employee.objects.create( employee_no=employee_no, name=name, department=department, position=Position.objects.filter(name=position_name).first(), ) success += 1 return success, errors

注意transaction.atomic在整段函数上,意味着即使中间记录了错误条目,成功创建的记录也会一并提交。如果你想实现"全部成功才提交",要把errors判断放在transaction之前。毕设里这个细节可以按自己的需求灵活调整,但逻辑一定要自洽。

6. 文档讲解与答辩策略:如何让代码变成可交付的完整项目

6.1 文档的四个部分应该怎么写

毕设源码交付时,文档往往比代码本身更能撑起答辩的成绩。我写文档时固定按四个部分组织:需求分析、系统设计、系统实现、系统测试。需求分析部分要画出角色用例图,描述管理员、HR、部门主管、普通员工各自的权限和操作范围;系统设计部分画数据库ER图,列出核心表结构;系统实现部分按员工管理、考勤管理、薪资管理、请假管理等模块详细介绍核心代码逻辑;系统测试部分给出测试用例表,包含功能测试和部分性能测试结论。

有人会觉得这四个部分太"模板化"了,但毕设文档的目的本来就是展示你已经完整经历了软件工程的思考过程。只要每个部分结合你实际的代码逻辑来写,就不是空洞的流水账。比如系统设计部分的ER图,你可以直接根据实际模型字段去画,一张总图加几张子模块图就是很好的内容量。

6.2 代码讲解的思路:先讲链路再讲细节

拿到一份完整的Django项目源码,如果不懂代码结构,答辩时很容易被评委问住。我推荐用"链路式讲解"代替"文件式讲解"。什么是链路式讲解?从用户发起一个请求开始,沿着URL路由、视图函数、模型、序列化器的顺序,把完整请求链走通一遍。例如讲员工列表功能,先展示前端页面的访问路径,再展示urls.py中的路由规则,再跳到ViewSet的list方法说明筛选逻辑,最后展示查询到的记录如何通过serializer转换成JSON返回给前端。

这个讲解方式最大的好处是,评委听到的是"功能如何实现"而不是"文件里有什么",逻辑通顺而且容易展开。在答辩前自己练习时,可以针对每个模块准备一到两个这样的链路,不需要背代码,只需要理解流程。比如考勤模块,讲打卡记录如何写入数据库、月度汇总如何用ORM聚合查询;请假模块,讲审批状态如何流转、权限如何控制。能讲清楚这些链路,代码是否是你完全手写其实不重要了,因为你对系统已经建立了真实的理解。

6.3 演示顺序与数据准备

答辩演示的节奏非常关键。我的建议是提前准备好一套完整的演示数据,严格按照"登录 -> 工作台统计 -> 员工管理 -> 部门管理 -> 考勤管理 -> 薪资管理 -> 请假审批"的顺序走。前两分钟让评委看到系统的整体概貌,中间各模块各花十几秒展示列表、筛选、新增、编辑这些核心操作,最后在请假审批环节展示一次角色权限的差异(用员工账号登录看不到审批入口,管理员账号能看到),收尾非常自然。

演示前要检查的硬性条件有三条:数据库迁移已执行且有初始化数据、runserver端口能正常访问、前端页面能正常加载静态文件。如果用了前后端分离方案,还要确认前端build之后的静态文件已经拷贝到Django的static目录,或者开发模式下前端代理端口正确。

6.4 从毕设源码到生产系统的扩展方向

最后给想用这套代码做进阶的同学指一条路。人事管理系统往生产环境走,需要补的东西有:考勤规则引擎(排班、迟到早退的计算逻辑)、加班审批流程、薪酬的自动计算公式、企业微信/钉钉的消息通知,以及报表的多维度导出。技术结构上,把约定好的业务逻辑从视图里抽出到Service层,把定时任务用django-celery-beat管理起来,把数据库索引按查询频率做优化。

把这条路走通,不只是完成一个毕设,而是真正理解了企业应用从0到1的过程。这部分内容如果写进论文的展望章节,也能看出你不仅完成了功能,还思考了系统的可持续演进。

7. 一些实际的体会与建议

如果你正在为"代码能跑但讲不清楚"发愁,我的建议是不要背代码,每天花半小时操作一遍系统,把每个页面的按钮都点一遍,观察URL和接口请求的变化,很快就能把整个系统的数据流串起来。这比反复读源码效率高得多,而且答辩时评委问任何功能,你都能立刻在系统里给他演示出来。

另一个经验是数据库设计一定要提前做好再写代码。我见过太多项目写到中途发现员工表少了个关键字段,然后到处改外键关联、改序列化器、改前端表单,改动量成倍增加。人事系统的表结构其实很成熟,先把ER图定下来,后面的编码就是体力活。运行期如果遇到问题,先看日志的具体报错,再判断是环境问题、数据问题还是逻辑问题,定位准确后再动手,不要"瞎改一通碰运气"。这套方法听着朴素,但真的能让你少走很多弯路。

返回列表