news 2026/10/10 7:39:23

Django+Flask搭建高校人事管理系统:架构设计与实践复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django+Flask搭建高校人事管理系统:架构设计与实践复盘

手上刚完成一套高校人事管理系统,正好趁热做个复盘。项目不算大,但“django-flask基于python的高校人事管理系统”这个标题,单看容易让人犯迷糊:到底是选Django还是Flask?我实际落地的时候,Django是主框架,Flask被安排成独立报表服务,两边共用同一个数据库,接口层面也做了明确切分。这么拆分不是炫技,而是业务逼出来的:人事系统的琐碎管理功能、权限体系、审批流,都适合交给Django这种“全家桶”框架;而月末集中爆发的复杂报表导出任务,单独拆出去用Flask跑,既不拖累主工程,后续资源不够还能单独扩容。

这套系统解决的问题很直接:让学校人事处彻底告别“Excel传来传去”的状态。教职工信息统一维护,入职离职异动走线上审批,工资数据按权限分层可见,各类统计报表自动生成,年底考核资料也不再翻遍聊天记录找版本。它适合正在做毕业设计或课程设计的同学参考,也适合打算给学校、事业单位做类似管理系统的开发者借鉴。下面把这轮的选型、建模、编码、部署过程,连同踩过的坑一起写出来。

1. 项目整体设计与选型思路

1.1 先理清业务边界,再谈框架选型

很多人一上来就在Django和Flask之间纠结,其实框架之争反而是最后的问题。人事管理系统表面看简单,拆开看至少有六大块:人员库、组织架构、考勤、薪资、异动审批、统计报表。每一块复杂度差异非常大。人员库和部门树是基础数据,操作频繁;考勤和薪资是敏感数据,权限边界必须卡死;异动审批流程长、状态多,需要严谨的状态机;统计报表平时没人看,月末全员一起点,又慢又卡就会被投诉。

我拿到需求之后先画了一张业务清单,把每个模块的操作频率、数据敏感度、复杂度列清楚。人员库要天天用,要求响应快,导入导出要顺手;审批流是低频操作,但每一步操作都要留痕;报表是月底集中访问,要求能扛住瞬时并发。这张清单直接决定技术选型,而不是先确定框架再倒推功能。

高校环境还有个隐藏约束:网络环境比一般企业复杂,有些楼宇之间网段隔离,信息办也可能提供统一身份认证服务。这些在一期未必用得上,但数据库字段和认证接口必须提前留好扩展位,否则后期对接统一身份平台的时候,改起来会非常痛。

1.2 Django 做主体,Flask 做配套报表服务

为什么主体用Django而不是Flask?主要原因是人事系统业务逻辑密集,Django的ORM、迁移、Admin后台、认证模块都是开箱即用的,开发效率高出一大截。Flask虽然灵活可控,但用户管理、权限、表单、后台管理都要自己搭,或者拼一堆第三方库,搭到一半就会发现工作量直接翻倍。

那Flask放在哪?我把它放在报表服务里。人事系统的报表场景很特殊:月底要导出各学院花名册、职称结构统计、年龄分布分析,Excel模板往往来自其他科室,字段顺序完全固定。如果这些报表逻辑全部写进Django主工程,报表相关的模板代码、Excel处理库会跟业务代码纠缠在一起,以后谁改谁头疼。单独做一个Flask服务,挂在同一个MySQL和Redis上,对外只暴露几个报表接口,既能复用数据,又能独立部署、独立扩容。

有人会问,为什么不用cron脚本或者Django的管理命令直接跑?关键问题是报表通常要人工选择条件,例如“导出信息学院2024年以后入职、副高以上职称人员”,这不是一句固定命令能解决的,需要可交互的接口。而文件生成是耗时操作,适合通过Redis队列把任务参数交给Flask Worker处理,比在主进程里开线程池更可控,也不容易出现内存泄漏把主服务拖垮。

1.3 技术栈与版本选型参考

这套系统实际使用的核心工具和版本如下,供后续参考:

组件版本承担角色
Python3.10基础运行环境
Django4.2 LTS主业务框架、ORM、认证、Admin
Flask2.3报表服务、文件导出
MySQL8.0主数据库
Redis7.x缓存、异步任务队列
Nginx + Gunicorn1.24 / 21.xWeb服务与反向代理

选Django 4.2是因为它是当时比较稳的LTS版本,不仅安全更新周期长,第三方库兼容性也更好。Python选了3.10而不是更新的版本,是因为部分内网服务器环境特殊,老版本编译依赖更容易满足。这里想提醒一句:千万别以为“Python版本越新越好”,真到部署阶段,服务器上能不能装、编译是否缺依赖,才是决定成败的地方。

2. 数据库设计与核心数据模型

2.1 人事系统绕不开的几张核心表

把人事处日常工作一步步拆开,数据库里最核心的就是这几张表:员工主表、部门表、岗位表、异动记录表、薪资表、用户账号表。我的设计原则是“主表精简、变更留痕、权限关联独立”。

员工主表存放教职工的静态基础信息,例如工号、姓名、性别、出生日期、学历、学位、入职时间、部门、岗位、职称、政治面貌、联系方式等。部门表负责组织架构,高校通常是多级树结构,比如学校下面分学院、处室,学院下面再分系部和教研室。异动记录表负责记录一个人从入职到离职的全过程,比如转岗、升职、调离、退休。薪资表存储月度工资相关数据,但绝不直接和员工账号权限绑定。用户账号表则单独存放系统登录账号,并关联角色和权限。

有人可能会问:“员工基本信息直接放在用户表里不就行了吗?”不行。业务账号和人事主数据必须分开,因为在职员工、离职员工、临时用工、外聘人员并不都需要登录系统,但他们的基本信息都必须存在于员工主表中。账号只是某个人访问系统的一把钥匙,不该和基础档案绑死。

2.2 员工主表设计的关键细节

员工主表是这个系统的地基,设计得不好,后面所有功能都会别扭。我实际进行过两次比较大的字段调整,总结经验如下。

工号字段要加唯一约束,并且不能允许为空。身份证号不要做主键,不要让用户手动输入身份证号,因为人工手输极容易出错,重号、错号都会给后续统计带来麻烦。工号最好由系统生成,比如根据入职年份和部门编号组合,生成后不可变更。

部门、岗位、职称这些信息,在员工主表里存放外键关联而不是直接存文本。文本的好处是显示方便,坏处是部门改名字或者岗位统一调整之后,历史数据对不上。用外键存ID,配合独立的部门表和岗位表,展示时再关联查询,数据才可控。

员工状态字段我建议用整型而不是字符串。0表示在职,1表示离职,2表示退休,3表示停薪留职,4表示试用期。为什么要用数字?因为字符串依赖记忆容易写错,而数字可以配合常量类统一管理,代码里写清楚了可读性完全没问题。同时保留一个激活标记字段,实现逻辑删除。

动态属性推荐使用JSON字段。高校人事系统最麻烦的就是字段不固定,今天要采集家庭住址,明天要补社保卡号,后天又可能增加一项海外经历。把这些不确定字段放到JSON字段里,比频繁改表结构省事得多。Django从3.1之后原生支持JSONField,MySQL 5.7之后也有JSON类型,使用非常顺手。

2.3 部门树和异动记录的建模

部门表设计时,我比较推荐两种方案之一。第一种是使用django-mptt这类现成树形库,它把父节点、子节点关系封装好,查询也非常方便。第二种是自关联parent字段加path字段,path字段存储从根节点到当前节点的完整路径,例如“/1/23/45/”,这样查询某个部门下所有子部门时,直接做前缀匹配即可。

高校人事管理系统里,部门树通常不会频繁变化,但会频繁查询。我用的是自关联加path字段,在path字段上建索引,统计和权限过滤时性能很稳定。担心树层级太深、路径字符串过长的问题不会出现,高校组织架构一般四到五级就到头了。

异动记录表的核心字段是:员工ID、异动类型、原部门、新部门、原岗位、新岗位、生效日期、审批状态、申请人、审批人、审批意见、审批时间。这里最容易踩的坑是“直接修改员工主表,不记录变更历史”。如果不记录历史,三个月后根本查不到“谁在几月从教学岗转到行政岗”的准确时间点,审计也找不到依据。每次异动必须在主表更新当前状态,同时在异动记录表写一条完整记录。状态流转则需要单独建一个审批记录表,每过一道审核就追加一行。

2.4 为什么一定要留审计字段

很多初学项目的人建表时只关注业务字段,很容易忽略审计字段。我在这套系统里每张表都加了这几个字段:创建人、创建时间、更新人、更新时间、逻辑删除标记。看起来是小事,真到出问题的时候能救命。

曾经有一次人事处的老师反馈说“某个教职工的职称被改错了”,如果不记录更新人,你根本不知道是谁改的、什么时候改的。有了updated_by和updated_at,直接翻审计日志就能定位,再配合操作日志表,还能追溯修改前后的字段值。另外,metadata逻辑删除标记配合默认过滤器,既能防止误删数据,又能在恢复数据时留有余地。

3. 核心功能模块实现要点

3.1 登录认证与权限控制

高校人事系统的权限场景比一般企业系统要复杂。除了“人事处管理员可以看所有人”这种粗粒度权限,还有大量数据级权限:各学院的院长只能看本学院员工,院系人事秘书只能维护本学院人员,教师本人只能看自己的部分信息。

实现上,我没有直接用Django默认的Group硬编码角色,而是建了角色表和权限表,采用RBAC模型。用户关联角色,角色关联权限,然后再在代码层做数据范围过滤。数据范围通过用户模型上的一个字段标识,比如a负责的部门就是部门树中某一个节点及其子部门。

认证这块,我预留了统一身份认证的对接入口,单独写了一个认证后端,处理校内统一账号体系。在没有接入统一认证之前,先用Django自带认证跑通业务,等接口条件具备再替换。这样做的意义是:系统上线初期不会被对接进度卡住,后期对接时也不需要推翻既有代码。

3.2 员工信息的Excel批量导入导出

学校人事处最常提的需求就是“把某某系统导出的Excel表导进系统”。这功能看着简单,做起来却容易翻车。我使用openpyxl处理Excel文件,因为对xlsx格式支持最稳,而且不依赖Office环境。

导入流程分成四步走。第一步下载模板,模板列头必须和系统字段一一对应;第二步读取用户上传的文件,先校验文件是否损坏、列头是否匹配;第三步逐行校验数据,比如工号不能为空、身份证号格式是否正确、部门名称能否在系统中找到;第四步把有效数据批量写入数据库,错误行则汇总成错误清单。

这里很关键的一点是:不要边读边写。正确做法是先全部校验完,再统一提交事务。否则前面十行写进去了,后面有一行格式错误,整个导入结果就会出现半成功状态,用户很难理解。我的做法是只要发现错误行就整体回滚,把错误清单导出给用户修改后重新上传。

导出Excel则相对简单,但要注意大数据量下的内存问题。导出全量人员名单时,几万条数据一次性塞进内存再生成Excel,很容易出现明显的卡顿甚至崩溃。正确做法是分页查询或者使用流式写入。我采取的方案是用pandas分块读取,然后通过openpyxl的write_only模式写入文件。

3.3 人事异动的审批流状态机

审批流是人事系统里最容易写乱的部分。我把它抽象成一套状态机逻辑,状态包括:待提交、待一级审核、待二级审核、已完成、已驳回、已撤回。操作包括:提交、通过、驳回、撤回、作废。

为了不引入复杂的工作流引擎,我直接用了一个整型状态字段,再写一个状态转移校验函数。每次操作前先判断当前状态是否允许该操作,不允许就直接抛异常。这样设计的好处是逻辑直观,调试容易,状态流转不清晰时会很快暴露问题。

异动审批的工作流,一个很重要的端点是:驳回之后,应该回到提交人上一次编辑的状态,而不是直接回到“待提交”。我在状态机里专门加了一个“驳回状态”概念,提交人可以在原有表单基础上修改后重新提交。另外,所有审批操作必须记录操作人、操作时间和意见,这些信息放在审批记录表里,后续生成审批轨迹时直接按时间排序即可。

3.4 薪资模块的数据脱敏

薪资数据是全系统敏感度最高的部分,这一块最容易出安全事故。我的处理方式是分层设计:数据库存储完整数据,Web接口层严格按角色过滤字段,前端展示层再做一次脱敏。

具体做法是,给薪资表增加一个权限等级字段,只有具备“薪资查看”权限的角色才能看到具体金额,普通教职工在工资查询页面只能看到应发、实发等汇总数字,不能看到其他同事的工资明细。导出工资单必须走审批流程,在生成的Excel文件里增加水印和自动清除功能,控制文件的传播范围。

实际过程中,我还做过一次部门数据越权的修复。当时一个学院的人事秘书账号理论上只能看本学院人员,但因为列表查询条件里漏加部门过滤,导致能翻到其他学院的人员工资。这个问题提醒我,凡是涉及数据级权限的功能,在代码审查时必须逐一核对查询条件,不能只依靠前端的按钮隐藏。

3.5 统计报表做到秒开

人事系统里的统计报表很多,例如部门人员数、学历结构、职称结构、年龄分布、入职年限分布等。最初直接写聚合查询,数据量小的时候毫无压力,但到了几万人规模加上多级部门筛选,页面最夸张时会达到两秒以上。

后来我做了几个调整。第一,常用报表每天凌晨用定时任务预生成到Redis缓存,用户在Web端直接读缓存。第二,月度大报表在月末生成结果并固化到数据库表,生成一次,之后所有查询只读结果表。第三,真正需要实时数据的场景,使用数据库原生聚合SQL,避免在Python层循环计算。

这套方案上线之后,绝大多数报表都能在几百毫秒内打开,月末高峰期也没有再出现明显卡顿。经验就一句话:报表数据优先考虑“预计算”而不是“实时查询”,用户根本不关心数据是不是一秒前生成的,他们只想知道能不能快点看到结果。

4. 关键代码与实操过程

4.1 项目初始化与环境准备

项目落地第一步是创建虚拟环境并安装依赖。这个环节看起来简单,但在高校内网环境里经常翻车,因为部分依赖包需要编译,编译过程缺依赖就失败。我实测下来比较稳的做法是先安装基础编译工具,再安装Python包。

python -m venv venv source venv/bin/activate pip install --upgrade pip setuptools wheel pip install django==4.2 flask==2.3 mysqlclient openpyxl redis gunicorn django-admin startproject hr_platform . python manage.py startapp hr_app python manage.py startapp org

mysqlclient这个包在内网环境很容易编译失败,如果服务器不方便装编译环境,可以直接改pymysql,或者用PyMySQL替代并在项目的__init__.py里做一些额外操作。我这里图省事,开发环境直接用mysqlclient,生产环境用同一套。

初始化项目之后,第一件事就是改settings数据库配置,把默认的SQLite改成MySQL。连接参数放在配置文件中,别写在代码里。

DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "hr_platform", "USER": "hr_user", "PASSWORD": "your_password", "HOST": "127.0.0.1", "PORT": "3306", "OPTIONS": { "charset": "utf8mb4" } } }

4.2 员工模型与Admin后台

员工模型是我最先写的一块,核心字段用Django模型定义如下,省略了部分扩展字段。

from django.db import models class Department(models.Model): name = models.CharField(max_length=100, verbose_name="部门名称") parent = models.ForeignKey( "self", null=True, blank=True, on_delete=models.CASCADE, verbose_name="上级部门" ) path = models.CharField(max_length=255, verbose_name="路径") sort = models.IntegerField(default=0, verbose_name="排序") class Meta: ordering = ["path", "sort"] class Employee(models.Model): emp_no = models.CharField(max_length=20, unique=True, verbose_name="工号") name = models.CharField(max_length=50, verbose_name="姓名") gender = models.CharField(max_length=10, choices=[("M","男"),("F","女")], verbose_name="性别") birth_date = models.DateField(null=True, verbose_name="出生日期") id_card = models.CharField(max_length=18, null=True, blank=True, verbose_name="身份证号") department = models.ForeignKey(Department, on_delete=models.PROTECT, verbose_name="所属部门") position = models.CharField(max_length=50, verbose_name="岗位") title = models.CharField(max_length=50, null=True, blank=True, verbose_name="职称") status = models.IntegerField(default=0, verbose_name="状态") extra = models.JSONField(default=dict, verbose_name="扩展信息") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.emp_no}-{self.name}"

这里有几个细节值得说明。部门外键我用了on_delete=models.PROTECT,意思是部门下面还有员工时,禁止直接删除部门。这个策略非常适合人事系统,防止误删组织节点以后,员工记录变成无根浮萍。员工状态字段使用整型,和前面说的数据模型设计对应,代码里用枚举常量类做映射。

Admin后台注册模型后,人事处的老师可以直接在后台维护基础数据,不用专门开发全套管理界面。

from django.contrib import admin from .models import Employee, Department @admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): list_display = ("emp_no", "name", "department", "position", "status") search_fields = ("emp_no", "name", "department__name") list_filter = ("status", "department")

4.3 Excel导入的代码套路

Excel导入逻辑不复杂,但细节很多。我贴一段基于openpyxl的核心实现思路,具体字段校验和业务逻辑可根据自己系统的模型调整。

import openpyxl from django.db import transaction def import_employees(file_obj): wb = openpyxl.load_workbook(file_obj, data_only=True) ws = wb.active headers = [cell.value for cell in ws[1]] required = ["工号", "姓名", "所属部门", "岗位"] for r in required: if r not in headers: raise ValueError("模板缺少必填列: %s" % r) rows = [] errors = [] for row in ws.iter_rows(min_row=2, values_only=True): data = dict(zip(headers, row)) emp_no = str(data.get("工号") or "").strip() name = str(data.get("姓名") or "").strip() if not emp_no or not name: errors.append("第%s行: 工号和姓名不能为空" % row[0].row) continue # 部门转外键 dept_name = str(data.get("所属部门") or "").strip() from .models import Department try: dept = Department.objects.get(name=dept_name) except Department.DoesNotExist: errors.append("第%s行: 部门不存在 %s" % (row[0].row, dept_name)) continue rows.append(Employee(emp_no=emp_no, name=name, department=dept)) if errors: # 整体回滚,不写入任何数据 raise ValueError("\n".join(errors)) with transaction.atomic(): Employee.objects.bulk_create(rows, ignore_conflicts=False) return len(rows)

这段代码只演示了核心流程,实际生产还需要补充身份证号格式校验、日期格式转换、重复工号检测、错误行号准确定位等。整体回滚的逻辑一定要保留,宁可导入失败,也不能制造半成功脏数据。

4.4 审批状态机的核心实现

状态机我封装在一个独立类里,用字典定义状态转移关系。每次操作都对当前状态做校验,再更新状态并记录历史。

class ApprovalState: DRAFT = 0 PENDING_FIRST = 10 PENDING_SECOND = 20 APPROVED = 30 REJECTED = 40 WITHDRAWN = 50 TRANSITIONS = { DRAFT: {"submit": PENDING_FIRST}, PENDING_FIRST: {"approve": PENDING_SECOND, "reject": REJECTED, "withdraw": WITHDRAWN}, PENDING_SECOND: {"approve": APPROVED, "reject": REJECTED, "withdraw": WITHDRAWN}, REJECTED: {"submit": PENDING_FIRST}, WITHDRAWN: {"submit": PENDING_FIRST}, } @staticmethod def can_transit(current, action): return action in ApprovalState.TRANSITIONS.get(current, {})

这段代码的好处是,审批流的规则全部用一张表表达,后续新增一个“会签”步骤,也只需要在这个字典里加状态和动作,不需要拆改大量if-else。审批记录表独立记录每一步操作,最终展示审批轨迹时,直接查历史记录即可。

4.5 Flask报表服务如何配合Django

Flask报表服务不承担业务展示,只处理一类任务:接收请求参数,生成Excel文件。它的入口接口接收参数和任务ID,处理完以后把文件地址回写Redis,前端轮询拿到结果后显示下载链接。

from flask import Flask, request, send_file import redis import json import os app = Flask(__name__) r = redis.Redis(host="127.0.0.1", port=6379, db=0) @app.route("/report/export", methods=["POST"]) def export_report(): data = request.get_json() report_type = data.get("report_type") dept_id = data.get("dept_id") file_path = generate_excel(report_type, dept_id) r.lpush("report_result", json.dumps({"task_id": data.get("task_id"), "path": file_path})) return {"status": "ok"} def generate_excel(report_type, dept_id): # 这里执行数据库查询和Excel写入 # 临时文件生成完成后返回路径 return "/tmp/report_result.xlsx" if __name__ == "__main__": app.run(port=5001)

生产环境不建议用Flask自带服务器,我实际是用Gunicorn单独跑这个报表服务。Django主服务把任务参数写入Redis队列,Flask服务从另一个端口监听并处理。如果报表任务量上升,只需要多起几个Flask进程,不碰Django主工程,部署和扩容都清晰。

5. 常见问题与排查技巧实录

5.1 部门树查询慢

部门树在数据量几百个节点时速度完全没有问题,可我第一次做出来的版本在页面部门选择下拉框里很卡。原因是前端把整棵部门树递归渲染成所有选项,加上权限过滤后,每次都做多次数据库查询。

后来我把部门树预加载到Redis缓存,缓存一次,半小时或一天刷新一次。同时前端改成懒加载模式,点开第一级才加载子级。优化后部门选择框从一两秒降到即时响应。很多问题的根源不在于数据库多复杂,而是查询次数太多,能减少数据库交互就尽量减少。

5.2 员工导入时身份证号变成科学计数法

Excel在处理18位身份证号时,如果单元格格式不是文本,会自动变成科学计数法。这个问题从Excel导入系统时几乎必现。我在导入函数中做了强制的字符串转换,读取单元格时用str()包一层,并且去掉可能出现的后缀内容。

更稳妥的办法是要求用户先下载系统模板,在模板里把身份证列设置为文本格式,同时提供打开文件时的类型检查。自己开发时千万别把身份证列自动识别为数字类型,否则导入导出反复转换几次,数据就全乱了。

5.3 权限控制遗漏了数据级范围

这是我自己踩过的一个比较严重的坑。当时已经实现了角色权限,每个学院人事秘书也能登录系统,列表页按钮也正常显示。但深挖查询接口时发现,学院人事秘书的查询条件里漏掉了部门过滤,理论上可以翻到其他学院的员工工资数据。

这类问题最难的地方在于前端页面显示正常,不点开具体接口根本发现不了。我最后在全项目做了一次权限专项审查,把所有涉及员工和薪资的接口,逐一核对是否携带了数据范围过滤。这里给一个建议:凡是涉及敏感数据的接口,必须在服务端查询语句里拼接数据范围条件,不能只依赖前端隐藏入口。

5.4 定时任务重复执行

报表预生成任务第一次上线时,我用crontab每分钟跑一次检查,结果某一天的月度报表任务被同时触发了两次,导致重复写入数据库结果表。后来我在任务入口增加了Redis分布式锁,只有获取到锁的实例才执行任务,执行完毕后释放锁。

import redis r = redis.Redis(...) lock_name = "report_monthly_lock" if r.set(lock_name, "1", nx=True, ex=600): try: generate_monthly_report() finally: r.delete(lock_name) else: print("已有任务在运行,本次跳过")

同样的问题也会出现在多个Gunicorn Worker并发启动定时任务时,分布锁是简便有效的方案。

5.5 数据库迁移把生产库搞挂

曾经有一回,为了给员工主表加一个扩展字段,直接在生产库执行了ALTER TABLE,结果因为表里有几万条数据,锁表了十几分钟,把正在上班使用的页面全部卡住。后来所有数据库变更都改成Django迁移文件方式,在低峰期执行,并且先备份。

SQLite和MySQL很多语法细节不同,比如字段类型、索引方式、字符串比较规则,开发环境没问题不代表生产环境没问题。如果项目起步用的是SQLite,上线前必须用真实数据量做一次迁移演练,千万别直接拿生产库练手。

6. 部署上线与运维避坑

6.1 服务器环境初始化

这套系统最终部署在内网一台Linux服务器上,使用Python3.10、MySQL8、Redis7、Nginx。环境初始化步骤大致如下:

sudo apt update sudo apt install -y python3.10 python3.10-venv python3.10-dev \ build-essential nginx mysql-server redis-server

之后创建专门的系统账号来运行应用,不要用root直接跑Python服务。应用目录、日志目录独立出来,权限分配清楚,这样出问题的时候排查往往会更容易。

6.2 Gunicorn与Nginx配置参考

Django主服务用Gunicorn跑,建议Worker数根据CPU核数配置。我这边是四核服务器,Gunicorn配置使用了四个worker加两个线程。配置参考如下:

gunicorn hr_platform.wsgi:application --bind 127.0.0.1:8000 --workers 4 --threads 2 --timeout 120

Nginx反向代理配置,静态文件和媒体文件由Nginx直接服务,动态请求转发给Gunicorn。

server { listen 80; server_name hr.example.local; location /static/ { alias /data/hr_platform/static/; } location /media/ { alias /data/hr_platform/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

Flask报表服务独立跑在5001端口,由另一个Nginx location转发到内部端口,或者不对外暴露,只允许内网服务调用。

6.3 备份、日志与安全加固

数据备份是这类系统的保命底线。我配置了每天凌晨的MySQL逻辑备份,保留最近三十天。备份文件直接同步到另一个文件服务器,避免本机硬盘故障导致数据全丢。

日志方面,把Gunicorn的访问日志和错误日志分别拆分,Django内部错误走标准logging输出到文件。排查问题的时候,日志字段越细分越方便,建议把用户ID、操作时间、请求路径、响应状态都记录下来。

安全加固虽然烦琐,但绝不能省:数据库账号禁止使用过于简单的密码,Django的SECRET_KEY和数据库密码放在环境变量文件里,不要提交到代码仓库;Nginx层面关闭不必要的目录浏览;系统对外只开放80/443端口,其他端口全部走内网。

6.4 这类项目的后续扩展方向

让我按照目前系统的开发情况,给几条参考的方向。第一是接入学校的统一身份认证,替换掉现在的本地账号体系,以后老师不用单独记人事系统密码,这是学校场景里非常实际的刚需。第二是增加消息通知模块,审批通过、工资发放、考核结果都能通过短信或内部消息推送给本人。第三是沉淀报表模板,把各二级学院经常使用的固定表格模板整理成在线配置,让人事处自己调整字段,而不是每次改模板都找开发。

其实这类系统的技术门槛不算高,真正花时间的都在业务理解和数据安全上。如果你打算自己动手做一套,我的建议是先把审批流和数据权限这两块设计清楚,其他功能可以边做边补。系统上线以后,被找得最多的一定不是某个功能不好用,而是“为什么他看到了不该看的数据”或者“这个审批单怎么流转乱了”,能把这两件事守住,系统就算成功了一大半。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 7:39:13

浏览器三大核心能力拆解:渲染管线、JS引擎与网络加载优化实战

你有没有遇到过这种场景:同一个活动页,在Chrome里几乎秒开,换到公司内部的旧内核WebView就白屏好几秒;同样的筛选逻辑,在旗舰机上丝滑流畅,在低端安卓机上却卡得像幻灯片。我最早以为是接口慢,后…

作者头像 李华
网站建设 2026/10/10 7:38:20

Win11 21H2高性能电源计划激活与深度调优指南

1. 这个“隐藏高性能模式”到底是什么?别被标题带偏了Win11 21H2系统里根本不存在一个官方命名、独立开关、图标可见的“高性能模式”。所谓“一条CMD命令搞定”的说法,本质上是把Windows电源管理中一个长期存在但默认不启用的底层策略——High Performa…

作者头像 李华
网站建设 2026/10/10 7:37:07

text-to-cad实战:从文本到三维CAD模型的自动生成技术解析

这两年“text-to-cad”这个词在三维建模圈子里出现的频率越来越高。简单说,就是你想一个零件长什么样,用几行文字描述出来,模型直接给你生成对应的CAD模型——不用打开软件一点点拉草图、标尺寸、做拉伸切除。我最早看到这类工作是在某次顶会…

作者头像 李华
网站建设 2026/10/10 7:36:37

Faiss不是向量数据库:向量检索引擎原理与生产避坑指南

1. Faiss不是“数据库”,而是专为向量检索而生的底层加速引擎Faiss这个名称在近两年的工程实践中出现频率极高,但很多人第一次听到时,下意识会把它和Elasticsearch、Milvus或Weaviate划上等号——认为它是个“带搜索功能的向量数据库”。这种…

作者头像 李华
网站建设 2026/10/10 7:36:13

计及调峰主动性的风光水火储多能互补协调优化调度

计及调峰主动性的风光水火储多能系统互补协调优化调度(Matlab代码实现)风光水火储多能系统这几个字,搞电力优化调度的人都不陌生,但"调峰主动性"这个视角,确实值得单独拿出来好好聊聊。很多人在做多能互补调…

作者头像 李华
网站建设 2026/10/10 7:36:07

AI智能体实战:自主容错控制与多模态应用全解析

很多人以为AI智能体就是那个“能聊天的对话框”,我以前也这么想。直到我用同样一个任务试了两种用法,结果一个花了半小时还答非所问,另一个三分钟就把活干完了——区别只在于,我是把它当“搜索引擎”使,还是当“团队成…

作者头像 李华