ARTICLE DETAIL

资讯详情

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

用Flask构建校园部门资料管理系统:从需求到部署全流程解析

用Flask构建校园部门资料管理系统:从需求到部署全流程解析 每年开学季我参与的学生会部门总会陷入同一种混乱换届后上一届的策划案、制度文件、成员名单散落在好几个人的网盘和QQ群里想找一份三年前的部门总结得在聊天记录里翻半天想统计一下部门成员信息Excel表格传来传去最后版本对不上。后来我用 Python 的 Flask 框架前后花了大概一周的业余时间做了一个校园部门资料学生组织管理系统把这些散落的资料和人员信息统一收进了一个轻量级的网页平台里。这篇文章就把这个项目的需求拆解、架构设计、数据库建模、核心代码和部署经验完整写出来给准备做类似管理系统、课程设计或者想入门 Flask 开发的朋友做个参考。这个系统面向的就是校园里各类学生组织、学生会部门、社团的管理场景核心解决两件事一是部门资料的统一归档与按权限查阅二是学生组织里成员和部门信息的规范化管理。技术上采用 Flask SQLAlchemy SQLite前后端一体用 Jinja2 模板渲染部署成本极低本地跑起来只需要几分钟。1. 项目背景与核心需求拆解1.1 校园部门资料管理的真实痛点做之前我先梳理了一下实际场景里的问题差不多有四个痛点这也是整个系统需求的最初来源。第一个痛点是资料分散且没有统一入口。各部门的策划书、新闻稿、制度文件、活动照片分散在个人电脑、网盘分享链接、微信群聊天记录里没有一个人能说清楚某个文件到底存在哪里。第二个痛点是权限模糊校级文件往往包含成员隐私信息学号、手机号、宿舍号不该对所有学生公开但实际传阅过程中很难控制。第三个痛点是换届交接困难老一届成员离任后新成员找不到历史资料活动经验和制度沉淀基本流失。第四个痛点是成员信息统计效率低部门编制几十到上百人不等靠 Excel 统计更新不及时格式还经常不统一。这些痛点决定了系统的功能边界一要有统一的资料上传、分类、下载入口二要有基于角色的访问控制至少区分普通成员和管理员三要有部门实体资料挂靠在部门维度下方便按组织架构检索四要有成员名册管理能记录入部时间、职务、学院班级这些基本信息。1.2 为什么选 Flask 而不是更重的框架在我接触过的 Python Web 框架里Flask 不是功能最全的但在这个场景下是最合适的。Django 自带 Admin 后台、ORM、认证体系功能确实强大可对这样一个规模很小的内部管理系统来说它的大部分能力是用不上的反而引入了一整套固定的项目结构和配置约定学习成本被拉高了。FastAPI 更偏向 API 服务和异步场景如果我们坚持用模板渲染做服务端页面它在这方面的生态反而不如 Flask 成熟。Flask 的核心特点就是灵活和轻巧一个应用可以从单文件起步随着需求增长再逐步拆成蓝图结构。这种渐进式组织方式非常适合校园项目因为刚开始你根本无法预料最终功能会长成什么样Flask 允许你自由组织代码不会把选择强加给你。另一个关键点是 Flask 对新手极其友好。路由和视图函数的概念非常直观app.route(/)下面挂一个 Python 函数就能返回页面配合 Jinja2 模板不需要专门写前端接口联调。另外Flask 有庞大且成熟的扩展生态Flask-SQLAlchemy 管数据库Flask-Login 管登录Flask-WTF 管表单校验这些都是经历了大量生产环境验证的组件。对校园项目来说直接站在这些成熟组件上能大幅减少自己造轮子带来的隐患。1.3 系统功能边界与角色设计做需求分析时我给自己定了一条原则宁缺毋滥。很多课设项目死在花哨功能上——又是图表又是消息推送结果核心逻辑一塌糊涂。这个系统我只保留五个核心模块。角色体系方面我设计了两种基础角色管理员和普通成员。管理员负责部门创建、成员审核、全局资料管理普通成员可以浏览公开资料、上传资料到所在部门、查看部门成员名单。如果组织架构更复杂可以再加一层部长角色用于审批本部门成员上传的资料但从最小可用产品的角度两种角色已经能覆盖大部分使用场景。功能模块方面包括部门管理增删改查设置部门简介、资料管理上传、分类、搜索、下载、权限控制、成员管理入部登记、信息维护、名单导出、公告管理发布系统通知和活动安排、个人中心修改密码、查看我的上传记录。搜索功能不搞花哨的全文检索直接用 SQLLIKE匹配标题和描述就够了在这个数据量级下性能绰绰有余。2. 系统架构与数据库设计2.1 架构选型前后端一体化是最务实的选择这个项目我选择了 Flask Jinja2 模板的服务端渲染架构没有拆前后端分离。原因很简单系统只有内部少数用户使用不需要高并发不需要移动端适配独立接口也没有专业前端配合。服务端渲染意味着 Python 直接控制页面输出表单提交一步到位Session 管理天然集成代码量比前端框架 REST API的方案少一半以上。如果拆成 Vue 或 React 前端就得处理跨域、Token 认证、接口文档维护对一个以资料存储为主的系统来说这是纯粹的成本堆砌。架构分层上我保留了经典的 MVT 结构模型层SQLAlchemy 模型定义表结构、视图层路由函数处理业务逻辑、模板层Jinja2 HTML 模板。数据库选用 SQLite 作为默认存储因为零配置、单文件、本地部署直接可用。如果之后要搬到服务器上多人同时使用可以把连接串换成 MySQL 或 PostgreSQLSQLAlchemy 让我们几乎不用改业务代码。这两个选择的组合正是轻量化网页端平台定位的体现。2.2 数据库表结构设计数据库是整个系统的地基表设计直接决定后续开发的顺畅程度。我设计了五张核心数据表用户表、部门表、资料表、成员记录表和公告表。表名关键字段用途说明usersid, username, password_hash, role, real_name, department_id系统登录账号role 区分管理员/成员departmentsid, name, intro, is_active部门实体如宣传部、组织部materialsid, title, description, dept_id, uploader_id, file_path, file_size, 文件类型, 是否公开, created_at部门资料的元数据membersid, user_id, dept_id, student_no, college, class_name, join_date, position, status成员花名册信息announcementsid, title, content, publisher_id, created_at系统公告用户表和成员记录表我刻意分开了。user 表管的是能登录系统的人member 表管的是这个部门里的花名册条目两者用 user_id 关联。这样设计的好处是一个用户可能先加入 A 部门后来转到 B 部门成员记录可以保留历史但登录账号始终只有一个。资料表里我加了一个 is_public 布尔字段默认公开管理员可以把含隐私信息的文件设为内部资料仅部门成员可见。另外设计了file_type字段用来在前端显示对应图标Word、PDF、Excel 等比每次拿后缀名现判断要省事。2.3 数据模型之间的关系与查询思路关系梳理上我就用最朴素的外键关联一个部门有多条资料一对多一个用户上传多条资料一对多一个用户对应一条成员记录一对一按当前部门状态。SQLAlchemy 里用db.relationship()反向引用查询时能非常自然地拿到关联数据。比如要查宣传部所有资料以及每个上传者的名字Python 代码里可以这样写materials Material.query.filter_by(dept_id1).all() for m in materials: print(m.title, m.uploader.real_name)m.uploader就是通过 relationship 自动关联的用户对象不需要手写 JOIN。但注意关联查询会触发额外的 SQL 查询数据量小的时候无感知如果资料表到了几万条就要考虑用joinedload做预加载。我在项目里专门留意了这一点。外键级联策略我也做了明确的约定删除部门时如果部门下还有资料或成员直接阻止删除并提示用户先转移或清理。这么做看着有点繁琐却能避免误操作导致的数据孤儿。数据丢失这种事一次就够你后悔很久。3. 核心功能模块实现3.1 用户认证与权限控制用户认证我一开始也想过直接用 Flask-Login但后来发现自己做 Session 认证更简洁代码也就二十多行而且能加深对原理的理解。登录成功后在session里写入user_id和role后续每次请求通过装饰器检查 Session未登录的请求直接重定向到登录页。密码存储必须用哈希绝不能存明文。Werkzeug 提供了generate_password_hash和check_password_hash配合pbkdf2:sha256算法在校验时调用from werkzeug.security import generate_password_hash, check_password_hash # 创建用户时 user.password_hash generate_password_hash(form.password.data) # 登录校验时 if check_password_hash(user.password_hash, form.password.data): session[user_id] user.id session[role] user.role权限控制需要区分两个层级登录权限和管理权限。我写了一个统一的装饰器模块登录用户才能访问的资料上传、下载接口挂login_required只有管理员能操作的部门管理、成员审核接口再叠加admin_required。3.2 部门资料上传与下载资料上传是这个系统的重中之重实现时要同时解决三个问题文件存哪里、文件名怎么处理、文件大小怎么限制。我在项目根目录创建了一个uploads/文件夹按部门编号分子目录例如uploads/1/存放宣传部资料、uploads/2/存放组织部资料。这样归类的好处是即使数据库记录丢失文件系统层面依然保持着清晰的目录结构排查问题时可以直接进文件夹看原始文件运维体验非常直观。文件名处理上用了 Werkzeug 的secure_filename函数。这个函数会自动过滤文件名里的路径分隔符和非法字符防止用户上传一个名为../../etc/passwd的文件导致路径穿越。但需要注意secure_filename对中文文件名不友好它会直接把中文去掉。所以我处理时会保留原始文件名存到materials表的original_name字段同时用时间戳生成了一个唯一的存储文件名from werkzeug.utils import secure_filename import os, time original_name file.filename ext os.path.splitext(original_name)[1].lower() stored_name f{int(time.time())}_{secrets.token_hex(4)}{ext} save_path os.path.join(UPLOAD_DIR, str(dept_id), stored_name) file.save(save_path)这样既规避了中文文件名带来的编码兼容问题也让系统内存储时不存在重名覆盖风险。下载时再把original_name作为响应头里的文件名返回用户拿到的还是原来的名字。3.3 成员信息管理成员信息管理模块我做了三件事入部登记、名单浏览与导出、离部标记。入部登记是一个表单填写学号、姓名、学院、班级、职务、入部时间提交后创建一条 member 记录同时自动关联当前登录用户的 user_id。如果这个用户之前已经存在一条未离部的成员记录我会提示该成员已在部门中避免重复录入。名单浏览支持按部门筛选和关键字搜索。分页功能用了 Flask-SQLAlchemy 自带的paginate方法每页显示 20 条底部生成页码导航。数据导出直接用 Python 标准库的 csv 模块生成 CSV 文件返回给浏览器不需要引入重量级 Excel 库import csv from io import StringIO from flask import Response def export_members(dept_id): members Member.query.filter_by(dept_iddept_id, statusactive).all() output StringIO() writer csv.writer(output) writer.writerow([学号, 姓名, 学院, 班级, 职务, 入部时间]) for m in members: writer.writerow([m.student_no, m.real_name, m.college, m.class_name, m.position, m.join_date]) return Response(output.getvalue(), mimetypetext/csv, headers{Content-Disposition: attachment; filenamemembers.csv})离部标记不是真删除而是把status字段改为inactive。这样历史成员的数据仍然保留在系统里却不会出现在当前名单中。换届时统计一届成员的流动情况直接按入部时间筛选就能拉出来非常实用。3.4 公告管理与数据看板公告模块相对简单就是管理员发布、成员列表查看。但我在首页做了一个数据看板把系统里的关键信息汇总展示出来部门总数、资料总数、成员总数、最近上传的五条资料、最新三条公告。这些数据用聚合查询即可不涉及复杂报表。首页展示时有个经验不要在模板里直接调Material.query.count()这种查询一两个可以将就多了就会造成 N1 查询。我是在视图函数里一次性查出所有统计数据打包成字典传给模板渲染这样数据库查询次数可控可预测。公告编辑就是用 CKEditor 类的富文本还是纯文本我选了纯文本加 Markdown 风格换行。项目定位是内部管理系统不是内容平台富文本编辑器会让模板转义和 XSS 防护变得复杂。纯文本配合nl2br过滤器足以满足需求。4. 关键代码与逻辑详解4.1 项目结构与蓝图组织Flask 项目最忌讳把全部代码塞进一个app.py。当路由超过十几个、模板超过十个单文件就会变得无法维护。我的项目结构采用了蓝图的模块化组织方式project/ ├── run.py # 启动入口 ├── config.py # 配置项 ├── requirements.txt ├── uploads/ # 上传文件目录 ├── app/ │ ├── __init__.py # 应用工厂 │ ├── models.py # 数据模型 │ ├── extensions.py # db 初始化 │ ├── main/ │ │ ├── __init__.py │ │ └── routes.py # 首页看板路由 │ ├── auth/ │ │ ├── __init__.py │ │ └── routes.py # 登录注册 │ ├── department/ │ │ ├── __init__.py │ │ └── routes.py # 部门管理 │ ├── material/ │ │ ├── __init__.py │ │ └── routes.py # 资料上传下载 │ └── member/ │ ├── __init__.py │ └── routes.py # 成员管理应用工厂模式是 Flask 官方推荐的结构它最大的好处是支持多实例创建测试时可以为测试环境单独建一个 appdef create_app(config_namedefault): app Flask(__name__) app.config.from_object(config[config_name]) db.init_app(app) from app.auth.routes import auth_bp from app.department.routes import dept_bp app.register_blueprint(auth_bp, url_prefix/auth) app.register_blueprint(dept_bp, url_prefix/department) return app每个蓝图里有自己的路由前缀比如登录相关的都是/auth/login、/auth/logout资料相关的都是/material/list、/material/upload。这样代码找起来非常直观哪个功能出问题直接进对应蓝图文件夹排查。4.2 核心数据模型定义数据模型我统一写在models.py里这里用 Flask-SQLAlchemy 定义四个核心模型。注意所有表都显式指定了__tablename__避免模型名和表名混淆带来的低级错误from datetime import datetime from app.extensions import db class User(db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(30), uniqueTrue, nullableFalse, indexTrue) password_hash db.Column(db.String(128), nullableFalse) role db.Column(db.String(10), defaultmember) # admin / member real_name db.Column(db.String(30)) created_at db.Column(db.DateTime, defaultdatetime.now) class Department(db.Model): __tablename__ departments id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(50), uniqueTrue, nullableFalse) intro db.Column(db.Text, default) is_active db.Column(db.Boolean, defaultTrue) created_at db.Column(db.DateTime, defaultdatetime.now) materials db.relationship(Material, backrefdepartment, lazydynamic) class Material(db.Model): __tablename__ materials id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(100), nullableFalse) description db.Column(db.Text, default) dept_id db.Column(db.Integer, db.ForeignKey(departments.id), nullableFalse) uploader_id db.Column(db.Integer, db.ForeignKey(users.id), nullableFalse) file_path db.Column(db.String(255), nullableFalse) original_name db.Column(db.String(255), nullableFalse) file_type db.Column(db.String(20), defaultother) file_size db.Column(db.Integer, default0) # 单位 KB is_public db.Column(db.Boolean, defaultTrue) download_count db.Column(db.Integer, default0) created_at db.Column(db.DateTime, defaultdatetime.now, indexTrue)定义uniqueTrue的字段如用户名和部门名称时我加了indexTrue因为唯一约束本身就会创建索引显式写出来反而能提醒自己哪些字段是高频查询条件。Material表上的created_at我也加了索引因为列表页默认按时间倒序排列索引能避免后期数据量大时的全表排序。4.3 资料上传的完整流程资料上传的视图函数是整个系统最需要注意细节的地方。我把它拆成四步校验登录权限、校验部门归属、保存文件、写入数据库记录。bp.route(/upload/int:dept_id, methods[POST]) login_required def upload_material(dept_id): department Department.query.get_or_404(dept_id) if not department.is_active: flash(该部门已停用无法上传资料, danger) return redirect(url_for(material.index)) title request.form.get(title, ).strip() description request.form.get(description, ).strip() is_public request.form.get(is_public) on file request.files.get(file) if not title or not file or not file.filename: flash(标题和文件均为必填项, warning) return redirect(request.referrer) if not allowed_file(file.filename): flash(不支持的文件类型请上传文档、表格或压缩包, warning) return redirect(request.referrer) # 保存上传到部署环境的具体目录 file_dir os.path.join(app.config[UPLOAD_DIR], str(dept_id)) os.makedirs(file_dir, exist_okTrue) original_name file.filename ext os.path.splitext(original_name)[1].lower() stored_name f{datetime.now().strftime(%Y%m%d%H%M%S)}_{secrets.token_hex(4)}{ext} file.save(os.path.join(file_dir, stored_name)) material Material( titletitle, descriptiondescription, dept_iddept_id, uploader_idsession[user_id], file_pathos.path.join(str(dept_id), stored_name), original_nameoriginal_name, file_typeext.lstrip(.), file_sizeos.path.getsize(os.path.join(file_dir, stored_name)) // 1024, is_publicis_public ) db.session.add(material) db.session.commit() flash(资料上传成功, success) return redirect(url_for(material.list, dept_iddept_id))注意我把file_path存的是相对路径而不是绝对路径这是为了部署时的可移植性。如果把绝对路径写进了数据库将来换服务器、换目录位置所有记录就都失效了。存相对路径后读取时在视图层拼一次绝对路径即可。4.4 文件类型与大小双重校验安全是资料管理系统绕不开的话题。用户能上传文件就意味着系统可能收到恶意文件。我只收文档、表格、压缩包和常见图片格式所以校验方式采用白名单扩展名 读取魔数双保险# 服务端白名单校验 ALLOWED_EXTENSIONS { pdf, doc, docx, xls, xlsx, ppt, pptx, zip, rar, 7z, txt, png, jpg, jpeg, csv } def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS # 读取文件头魔数作二次校验 def check_file_magic(file_stream, ext): magic_map { pdf: b%PDF, png: b\x89PNG, jpg: b\xff\xd8, zip: bPK\x03\x04, } if ext not in magic_map: return True # 没有魔数映射的扩展名仅依赖扩展名白名单 header file_stream.read(4) file_stream.seek(0) return header.startswith(magic_map[ext])文件大小限制方面Flask 的MAX_CONTENT_LENGTH配置项可以控制请求体上限。我在 config 里设置了 20MB超过这个大小的请求会直接返回 413 错误再配合上传时的文件大小显示前端用户也能清楚地看到文件太大被拒收的原因。有了这些安全处理系统才能放心地开放上传给全校各个部门使用。不然一个伪装成 Word 的可执行文件上传到服务器上一旦被下载运行整个内网安全就全完了。5. 部署与运行5.1 本地开发环境搭建部署的第一步是保证本机 Python 环境正确。建议直接用官方安装包安装 Python 3.10 或 3.11 版本安装时务必勾选Add Python to PATH这个选项默认不勾不勾的话在终端里输入python会直接提示命令不存在。环境装好后进入项目目录创建虚拟环境并安装依赖cd project python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install -r requirements.txtrequirements.txt 里的核心依赖只有这几个flask、flask-sqlalchemy、gunicorn。如果有表单相关的需求再加flask-wtf。整个项目依赖不到十个包这也是轻量化的另一个体现。安装完成后初始化数据库flask --app run.py shell数据库初始化我用了一个简单的init-db命令在 run.py 里注册一个自定义 CLI 命令执行建表和写入默认管理员账号。这样每次部署新环境只需一条命令就能准备就绪。启动开发测试python run.py浏览器访问http://127.0.0.1:5000看到登录页就说明系统跑起来了。本地这个阶段不要用 gunicorn 或 nginx她们是生产环境用的开发调试用 Flask 内置服务器就够了代码修改后还能自动重载。5.2 生产部署Gunicorn Nginx真正要给学生会几个部门同时使用就不能再用 Flask 开发服务器了它的性能和安全加固程度都不足以应对公网环境。我的做法是 Flask 应用用 Gunicorn 跑前面再挂一层 Nginx 负责静态文件托管和反向代理。先安装 Gunicornpip install gunicorn然后用一行命令启动gunicorn -w 4 -b 127.0.0.1:8000 run:app-w 4表示启动四个 worker 进程run:app指run.py文件里的app实例。注意 Gunicorn 只监听内网地址 127.0.0.1:8000不让它直接暴露在公网公网流量先到 Nginx再由 Nginx 转发给 Gunicorn。Nginx 配置一个站点server { listen 80; server_name your-server-ip; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /path/to/project/static/; } }上传文件目录uploads/也需要通过 Nginx 做访问控制不能让文件直链绕过登录校验。我在视图层封装了一个/material/download/int:material_id的路由所有下载请求走 Flask 权限检查后再返回文件流而不是直接暴露静态路径。为了让服务在服务器重启后还能自动拉起我还写了个 systemd 服务单元Gunicorn 作为后台服务托管这样即使进程意外退出systemd 也会把它拉起来不用人工盯守。5.3 部署踩坑记录部署过程中我自己踩过几个坑写出来帮大家少走弯路。第一个坑是数据库文件路径问题。SQLite 数据库文件如果配置成相对路径不同启动方式解析出的工作目录不一样就会导致本地开发时建的表到服务器上不认。解决办法是在 config 里用绝对路径指定数据库位置BASE_DIR os.path.abspath(os.path.dirname(__file__)) SQLALCHEMY_DATABASE_URI sqlite:/// os.path.join(BASE_DIR, data.db)第二个坑是中文乱码。Windows 终端默认编码不是 UTF-8直接print中文或往文件里写中文可能出现 UnicodeEncodeError。解决办法是在 run.py 开头设置标准输出编码或者干脆在 Linux 服务器上部署省掉很多编码烦恼。第三个坑是上传目录权限。Nginx 或 Gunicorn 运行时用的系统用户如果对 uploads 目录没有写权限上传文件会直接报 PermissionError。我当时排查了半天最后发现就是目录 owner 不对。处理方式是提前把 uploads 目录的属主改成运行用户并设置合适的权限。6. 常见问题排查与优化建议6.1 高频问题速查表把项目使用过程中最容易遇到的问题整理成一个速查表按症状、可能原因、解决方法三列排列排查时可以直接对照。症状可能原因解决方法页面 404访问路径不对蓝图注册的 url_prefix 和路由拼接出错检查app.register_blueprint(bp, url_prefix...)前缀上传文件后列表不显示file_path存的是绝对路径部署后目录变化改成存相对路径并在读取时拼接中文文件名变长串数字英文secure_filename过滤了非 ASCII 字符保留original_name存储名改用时间戳文件上传报 413 错误超过了MAX_CONTENT_LENGTH限制调大配置项前端同时提示大小限制登录后刷新就掉登录态Session 使用了默认密钥重启后失效设置稳定的SECRET_KEY环境变量页面能开但 CSS 不生效静态文件请求未代理到 Flask 或路径不对Nginx 配置 location /static/ 指向正确目录删除部门时报外键错误部门下有资料或成员记录改为逻辑停用is_activeFalse不物理删除资料超过几百条后列表变慢没有分页或未建立索引用paginate分页给查询字段加索引这些坑基本覆盖了一个 Flask 项目从开发到上线的完整链路遇到问题先对照表排查能省下大量翻文档的时间。6.2 性能与安全优化建议项目运行稳定后有几个优化方向值得做。性能上当前数据量几个 G 以内SQLite 完全扛得住但需要关注的是查询的 N1 问题列表页如果显示每条资料的上传者姓名千万不要在模板里通过关系属性逐个访问用户表。用joinedload预加载from sqlalchemy.orm import joinedload materials Material.query.options(joinedload(Material.uploader)).all()安全上第一件事是给所有 POST 请求加 CSRF 防护Flask-WTF 的CSRFProtect扩展可以全局启用模板里对应表单加入csrf_token()即可第二件事是给跳转链接添加校验防止开放重定向漏洞第三件事是定期备份 SQLite 数据库文件我写了个简单的备份脚本每天凌晨用 cron 定时执行sqlite3 data.db .backup backups/data_$(date %F).db。数据是这个系统最有价值的资产备份永远不嫌多。6.3 后续扩展方向如果这个系统要继续往下做我建议按顺序扩展以下功能一是通知栏的消息订阅和未读角标二是部门资料的版本管理同一份策划案修改后保留历史版本三是操作日志记录谁在什么时间上传或删除了什么文件方便追责四是生成简单的统计报表比如各部门上传资料数量排行、成员活跃度排名。这些功能都不需要改架构在现有蓝图和模型基础上加表加路由即可完成。个人体会最深的一点是做这种管理系统功能不是越多越好边界清晰、权限严谨、部署简单才是真正的竞争力。花哨的图表展示远不如一个稳定的下载链接有价值。我这个项目累计使用人数不到一百但解决了资料归档和人员管理的核心问题就达到了上线预期。后续如果你也想做类似的系统可以从最小的用户认证加资料列表起步跑通之后再逐步加部门、吧成员、加公告迭代路径非常清晰。
返回列表