☰
Django与Flask双框架实战:档案室管理系统设计与部署
2026/10/10 10:36:17 网站建设 项目流程

档案室管理系统算不上什么高精尖项目,但它是典型的“业务不复杂、规矩却一大堆”的系统。档案的录入、编号、存放、借阅、归还、密级管控、借阅记录追溯,每一项拆开看都很简单,合在一起却很容易做成一个“能跑但不好用”的东西。这个项目用 Python 双框架的方案来做:Django 承担主业务系统,Flask 以子服务形式承担文件预览、格式转换这类轻量任务,整体架构清爽,代码可维护性高,适合作为中小型单位档案数字化的基础框架。下面我把整个设计思路和落地过程完整梳理一遍。

1. 整个系统为什么选择 Django 和 Flask 双框架

1.1 Django 与 Flask 的分工定位

单看字面,Django 和 Flask 像是二选一的关系,而这个项目实际上把它们放在了一起用。这种选择不是炫技,而是根据业务形态做的取舍。

Django 自带 Admin 后台、ORM、迁移机制、表单校验和完整的认证体系,做档案管理系统这种“数据录入量大、后台管理需求强、权限分级明确”的业务非常顺手。档案管理员要在后台批量录入档案、维护存放位置、审批借阅申请,这些场景几乎就是 Django Admin 的主场。用 Django 写档案管理,数据库模型、管理后台、登录认证这些基础设施可以省掉大量重复开发时间。

Flask 则用于文件预览与格式转换服务。档案系统里往往需要在线预览扫描件,常见的文档格式包括 PDF、图片、Word 等。这些操作的特点是请求密集、单次任务耗时短、逻辑相对独立,不太适合塞进 Django 的主进程里。把它独立成一个 Flask 子服务,一是防止大文件转换阻塞主业务请求,二是这部分的代码可以单独维护、单独扩展,不影响主系统的稳定性。

两个框架同时使用,最关键的一点是数据库要共享一份。Django 负责业务数据的读写,Flask 只负责读取文件信息和生成预览结果,不直接操作核心业务表。这样既保留了两者各自的开发效率,又不会因为双框架引入复杂的数据一致性问题。

1.2 双框架之间的协作机制

双框架协作的核心就是“Django 管数据、Flask 管文件”。实际落地时采用共享数据库加内部 HTTP 接口的组合方式。

共享数据库是基础。档案主表、借阅记录、用户权限表都在同一套数据库里,Django ORM 负责所有写操作和大部分读操作,Flask 需要读数据时通过 Django 提供的只读接口获取,不直接连数据库。这样有什么好处?数据模型只要维护一份,不存在同步问题,也不用在两套代码里各自维护表结构。

内部 HTTP 接口负责调用链条。流程是这样的:

用户在 Django 系统里点击“预览档案”,Django 先校验当前用户的权限,确认有权限后,在内部请求 Flask 的预览接口,接口地址形如http://127.0.0.1:5002/preview?file_id=xxx&token=xxx,Flask 收到请求后,先通过 token 向 Django 验证调用方身份,再读取对应的磁盘文件,生成预览内容返回给 Django,最终由 Django 把预览结果嵌入页面呈现给用户。

拆到这里,可能有人会问:为什么不直接用 Celery 来做异步任务,非要单独起一个 Flask 服务?原因是档案预览这个场景的并发量并不高,但延迟敏感,用户点了就要尽快看到结果。Celery 的任务队列适合“量大、可排队、不着急”的任务,比如批量导入档案、定时生成报表。而文件预览更像是“按需同步计算”,直接用一个常驻的 Flask 服务最轻快,省去消息队列的运维成本。

1.3 适用边界:双框架不是万金油

强调一下,这套双框架架构适合的是中小型项目,不是所有项目都该这么拆。

如果档案系统规模很小,档案量在几千份以内,用户数不超过几十人,那直接用 Django 单框架就够了。多引入一个 Flask 服务意味着多一个进程要部署、多一套日志要排查、多一层接口要维护,这部分成本在小项目里是纯负担。

反过来,如果系统规模再大一级,达到多分支机构、高并发访问、海量档案文件,那这种简单的双框架沟通方式也不够用,更合理的是把文件服务用专门的分布式存储和独立后端来实现。所以说,这个架构是在“单体会过重、微服务又过轻”之间的一个平衡点。

2. 档案业务的建模思路与核心数据表设计

2.1 档案管理的业务对象拆解

做档案系统之前,先把档案管理里的核心概念理清楚。档案系统里大体上有这几类角色和对象。

角色层面:系统管理员负责建账号、分配权限,档案员负责档案的入库、编目、上架,普通用户可以检索和发起借阅。不同角色对应不同的操作范围,这是权限设计的基础。

业务对象层面:档案本身是核心,档案的属性包括档号、题名、类型、密级、存放位置、状态等。借阅记录是第二重要的对象,记录谁在什么时间借了哪份档案、审批人是谁、什么时候还的。存放位置的管理虽然简单,但必不可少,档案柜、层格、位置编码都要落到数据模型里。

还有一个比较容易被忽略的是操作日志。系统里每一次登录、检索、借阅、审批都应该记日志。档案管理的合规要求比较严格,一旦出现档案去向争议,日志是唯一能还原过程的数据。这个不是锦上添花,是刚需。

2.2 Django 数据模型的落地代码

下面直接给一份经过实际调整的模型代码。档案表是最核心的,字段设计充分考虑了一线使用时的录入习惯。

from django.db import models from django.contrib.auth.models import User class Archive(models.Model): ARCHIVE_TYPES = [ ('contract', '合同'), ('personnel', '人事'), ('financial', '财务'), ('project', '项目'), ('other', '其他'), ] SECRET_LEVELS = [ ('public', '公开'), ('internal', '内部'), ('secret', '秘密'), ] STATUS_CHOICES = [ ('in_storage', '在库'), ('borrowed', '借出'), ('destroyed', '销毁'), ] archive_no = models.CharField('档号', max_length=64, unique=True) title = models.CharField('题名', max_length=200) archive_type = models.CharField('档案类型', max_length=20, choices=ARCHIVE_TYPES) secret_level = models.CharField('密级', max_length=10, choices=SECRET_LEVELS, default='internal') location_code = models.CharField('存放位置', max_length=64) file_path = models.CharField('文件路径', max_length=512, blank=True) status = models.CharField('当前状态', max_length=20, choices=STATUS_CHOICES, default='in_storage') created_by = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name='录入人') created_at = models.DateTimeField('录入时间', auto_now_add=True) class Meta: ordering = ['-created_at'] def __str__(self): return self.archive_no class BorrowRecord(models.Model): archive = models.ForeignKey(Archive, on_delete=models.PROTECT, verbose_name='档案') borrower = models.ForeignKey(User, on_delete=models.PROTECT, related_name='borrow_records', verbose_name='借阅人') approver = models.ForeignKey(User, on_delete=models.PROTECT, related_name='approved_records', verbose_name='审批人', null=True, blank=True) borrow_date = models.DateTimeField('借出时间', null=True, blank=True) return_date = models.DateTimeField('归还时间', null=True, blank=True) due_date = models.DateTimeField('应还时间') status = models.CharField('状态', max_length=20, choices=[ ('pending', '待审批'), ('approved', '审批通过未领取'), ('borrowed', '借出中'), ('returned', '已归还'), ('rejected', '已拒绝'), ], default='pending') def __str__(self): return f'{self.archive.archive_no} - {self.borrower.username}'

字段设计上有个细节值得说一下:档案表里的file_path虽然保存了文件在磁盘上的路径,但对外绝不直接暴露。用户要查看文件时,永远通过接口访问,不直接拼接路径。这是文件安全的第一道防线。

location_code的格式我建议统一为“库房-排-列-层”这样的层级编码。比如A-01-03-02表示 A 库房第 01 排第 03 列第 02 层。这套编码规则虽然简单,但在盘点时非常实用,拿着清单就能直接找到对应位置。

2.3 档号生成规则与并发防重

档号是档案的唯一业务标识,一旦生成就是终身编号,不能复用。常见的规则是“类型代码 + 年份 + 流水号”,比如HT-2025-0001。

这里有个实际操作中的坑。如果在 Django 视图里先用档案类型和年份查最大流水号,再加一生成新档号,并发请求时很容易出现两个请求查到同一个最大号,生成相同档号,导致入库失败或者数据错乱。

解决方式有几种,我实际用的是事务加行锁的方案:

from django.db import transaction from django.db.models import Max def generate_archive_no(archive_type): with transaction.atomic(): counter, created = ArchiveNoCounter.objects.select_for_update().get_or_create( type_code=archive_type, year=datetime.now().year, defaults={'current_no': 0} ) counter.current_no += 1 counter.save() return f"{archive_type.upper()}-{datetime.now().year}-{counter.current_no:04d}"

这里关键就是用select_for_update()把对应年份的计数器行锁住,保证并发环境下只有一个请求能拿到加一后的流水号。如果只靠Max()查询来解决并发问题,迟早要踩坑,这个坑基本必踩。

3. 项目工程结构与双框架实操落地

3.1 工程目录结构规划

项目采用一个仓库管理两套代码的方式,目录结构如下:

archive_system/ ├── django_server/ │ ├── manage.py │ ├── config/ │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ └── apps/ │ ├── archives/ │ │ ├── models.py │ │ ├── views.py │ │ ├── urls.py │ │ └── admin.py │ └── users/ │ ├── models.py │ └── views.py ├── flask_service/ │ ├── app.py │ ├── preview.py │ ├── utils.py │ └── requirements.txt ├── scripts/ │ └── init_db.py ├── media/ │ └── archive_files/ └── requirements.txt

这种结构的好处是两套代码虽然分开部署,但在同一个仓库里方便管理。django_server是主服务,flask_service是辅助服务,media/archive_files是档案文件存储目录。如果后续要把文件存储切到对象存储,只需要修改 Flask 服务里的文件读写部分,Django 侧几乎不用动。

3.2 Django 侧的档案上传与入库接口

档案入库是档案员最高频的操作。上传接口不仅做文件保存,还要做格式校验和基础信息登记。下面这段是简化后的上传接口代码:

import os from django.conf import settings from django.http import JsonResponse from django.views.decorators.http import require_POST from .models import Archive @require_POST def archive_upload(request): if not request.user.has_perm('archives.add_archive'): return JsonResponse({'code': 403, 'msg': '无操作权限'}, status=403) archive_no = request.POST.get('archive_no') title = request.POST.get('title') archive_type = request.POST.get('archive_type') secret_level = request.POST.get('secret_level', 'internal') location_code = request.POST.get('location_code') upload_file = request.FILES.get('file') if not all([archive_no, title, archive_type, location_code, upload_file]): return JsonResponse({'code': 400, 'msg': '请填写完整信息'}, status=400) # 文件类型白名单校验 ext = os.path.splitext(upload_file.name)[1].lower() allowed_ext = ['.pdf', '.jpg', '.jpeg', '.png', '.doc', '.docx'] if ext not in allowed_ext: return JsonResponse({'code': 400, 'msg': '不支持的文件格式'}, status=400) # 文件按档号归档存储 relative_path = os.path.join(archive_type, archive_no + ext) full_path = os.path.join(settings.MEDIA_ROOT, 'archive_files', relative_path) os.makedirs(os.path.dirname(full_path), exist_ok=True) with open(full_path, 'wb+') as f: for chunk in upload_file.chunks(): f.write(chunk) archive = Archive.objects.create( archive_no=archive_no, title=title, archive_type=archive_type, secret_level=secret_level, location_code=location_code, file_path=os.path.join('archive_files', relative_path), created_by=request.user, ) return JsonResponse({'code': 0, 'msg': '入库成功', 'data': {'archive_id': archive.id}})

这里重点说下文件保存路径。用archive_type/archive_no.ext的结构,即按档案类型分目录,文件名直接用档号。这样在磁盘上找文件非常直观,运维排查问题时不需要查数据库就知道文件在哪。还有一个细节,Django 的MEDIA_ROOT和项目目录在部署时要注意区分开,档案文件应该存储在独立的数据盘,不要和代码混在一起。

入库接口要校验权限add_archive,这个权限是 Django 默认模型权限的一部分,在创建 Model 后自动生成,直接用来做接口级权限控制很方便。

3.3 档案预览的 Flask 子服务实现

Flask 子服务的核心功能是文件预览。这里以 PDF 预览为例,给出一个完整的实现:

from flask import Flask, request, jsonify, send_file import requests import os app = Flask(__name__) DJANGO_AUTH_URL = 'http://127.0.0.1:8000/api/verify-file-token/' FILE_BASE_DIR = '/data/archive_files' SERVICE_TOKEN = 'a_secret_service_token' @app.route('/preview/pdf') def preview_pdf(): file_path = request.args.get('file_path') token = request.args.get('token') if not file_path or not token: return jsonify({'code': 400, 'msg': '参数缺失'}), 400 # 向 Django 验证 token 和文件权限 resp = requests.post(DJANGO_AUTH_URL, json={ 'token': token, 'file_path': file_path, 'service_token': SERVICE_TOKEN, }) if resp.status_code != 200: return jsonify({'code': 403, 'msg': '验证失败'}), 403 full_path = os.path.join(FILE_BASE_DIR, file_path) if not os.path.exists(full_path): return jsonify({'code': 404, 'msg': '文件不存在'}), 404 return send_file(full_path, mimetype='application/pdf')

这里有两个细节要特别注意。第一个是路径拼接的越界问题,file_path如果来自用户输入,必须校验不能包含..或绝对路径,否则可能会被恶意利用读取系统其他文件。更稳妥的做法是传档案 ID 而不是文件路径,让 Flask 通过 Django 接口拿到可信的路径。

第二个是 token 的设计,既要校验用户的身份,也要校验调用方确实是 Django 主服务。上面的service_token就是服务间认证的凭证,部署时放到环境变量里,不要硬编码到代码中,否则代码仓库一旦泄露,整个子服务就等同于裸奔。

3.4 借阅流程的闭环实现

借阅是档案系统里业务流程最长的一个模块,从申请到审批到借出到归还,每一步都要有状态控制和记录。状态流转我用的是上面模型里设计的五状态模型:

待审批 -> 审批通过未领取 -> 借出中 -> 已归还 \-> 已拒绝

借出操作的核心代码要注意库存校验和状态一致性:

from django.db import transaction @transaction.atomic def borrow_archive(record_id, user): record = BorrowRecord.objects.select_for_update().get(id=record_id, borrower=user) if record.status != 'approved': return {'code': 400, 'msg': '当前状态不可借出'} archive = Archive.objects.select_for_update().get(id=record.archive_id) if archive.status != 'in_storage': return {'code': 400, 'msg': '档案已借出或不可用'} archive.status = 'borrowed' archive.save() record.status = 'borrowed' record.borrow_date = timezone.now() record.save() return {'code': 0, 'msg': '借出成功'}

这里我用了两个select_for_update(),一个锁借阅记录,一个锁档案记录。为什么要锁档案?因为同一份档案可能同时有多条“待审批”的申请,如果不加锁,两个审批人可能同时对同一份档案执行借出操作,前一个刚借出,后一个又把它借出去了,逻辑上就乱套了。加锁之后,第二个操作会等待第一个事务提交,再读取到最新的borrowed状态,然后正确返回“已借出”。

审阅的时候要注意,这个事务里顺序不能乱,必须先锁BorrowRecord再锁Archive,如果两个接口的加锁顺序不一致,高并发下容易造成死锁。虽然这种小系统遇到死锁的概率不高,但养成一致的加锁顺序是个好习惯。

4. 权限控制、检索优化与文件安全

4.1 档案密级与角色的交叉权限控制

档案系统的权限不能只看角色,还要看档案的密级。用户能不能看一份档案,取决于两个维度:角色的权限等级和档案的密级等级。

Django 自带的@login_required和@permission_required只能解决“能不能进这个功能”的问题,解决不了“能不能看这份档案”的问题。实际的判断逻辑需要自己写。这里给出一个权限判断的示例:

from django.core.exceptions import PermissionDenied SECRET_LEVEL_RANK = { 'public': 1, 'internal': 2, 'secret': 3, } ROLE_MAX_RANK = { 'admin': 3, 'archivist': 2, 'reader': 1, } def check_archive_read_permission(user, archive): role = get_user_role(user) max_rank = ROLE_MAX_RANK.get(role, 0) archive_rank = SECRET_LEVEL_RANK.get(archive.secret_level, 0) if archive_rank > max_rank: raise PermissionDenied('当前账号无权查看该密级档案') return True

这套逻辑的核心思路是:普通用户(reader)最多只能看“公开”和“内部”档案,档案员可以看“秘密”档案,管理员有最高权限。实际项目中,这个角色和密级的关系可能需要更复杂的配置,比如某个特定用户组可以额外查看某类密级,这时可以加一个“组-密级”的关联表来配置。

需要注意,这个校验不仅要应用在档案详情页,还要应用在检索接口。用户用关键词搜档案时,如果检索结果里混入了高密级档案,即使详情页有拦截,也在列表页泄露了“存在这样一份档案”的信息。正确的做法是在检索 SQL 阶段就把密级条件拼进查询,而不是等结果出来再过滤。

4.2 检索功能设计与关键词权重排序

档案检索不是全文搜索引擎那种大而全的检索,它更强调“快速找到已知的某份档案”。因此,检索条件的组合能力比单纯的关键词深度更重要。

实际实现上,检索引擎需要支持档号精确匹配、题名模糊匹配、档案类型筛选、密级筛选、时间范围筛选这几类条件的组合。Django ORM 的Q对象可以很好地完成这个组合查询:

from django.db.models import Q def search_archives(user, keyword, archive_type=None, secret_levels=None): queryset = Archive.objects.all() if keyword: queryset = queryset.filter( Q(archive_no__icontains=keyword) | Q(title__icontains=keyword) ) if archive_type: queryset = queryset.filter(archive_type=archive_type) if secret_levels: queryset = queryset.filter(secret_level__in=secret_levels) return queryset.order_by('-created_at')

这里使用了icontains做不区分大小写的模糊匹配。对于档案数量在几万份以内的系统,这种查询性能足够用了,不需要单独引入 Elasticsearch。如果档案量真的到了几十万上百万,优先考虑 MySQL 内置的全文索引,而不是直接上搜索引擎,复杂度会小很多。

关于关键词排序,有一个比较实用的技巧:档号完全匹配的结果优先级最高,其次是题名匹配,最后才是正文或其他字段匹配。这个规则可以用 Django 的Case和When实现:

from django.db.models import Case, When, IntegerField queryset = queryset.annotate( relevance=Case( When(archive_no__exact=keyword, then=3), When(title__icontains=keyword, then=2), default=1, output_field=IntegerField(), ) ).order_by('-relevance', '-created_at')

这个排序规则看起来很简单,但实际使用下来效果非常好。用户检索时,最大概率是档案员拿着纸质档案号来系统里找电子归档,档号精确匹配排最前面是符合使用习惯的。

4.3 文件访问链路中的安全设计

档案文件是系统里最敏感的数据资产。文件访问链路的安全设计,我从两个方面做了控制。

一方面,所有档案文件都不放在 Django 的static或media目录下对外暴露。Django 在开发模式下虽然能通过MEDIA_URL直接访问 media 目录下的文件,但档案文件绝对不能这么干。正确的做法是文件存储在媒体根目录外的独立路径,通过 Flask 子服务提供受控的访问接口。

另一方面,Flask 子服务的接口做了双层校验。第一层是用户 token 校验,用户登录 Django 后获得一个短期有效的 token,这个 token 在访问预览接口时会被 Django 验证。第二层是服务间校验,Flask 只接受携带正确service_token的请求,防止外部绕过 Django 直接调用 Flask 接口拿文件。

接口访问的完整流程是:

  1. 用户请求 Django 的“预览档案”页面。
  2. Django 确认用户有权限访问该档案。
  3. Django 生成一个短期 token,并携带用户的身份信息。
  4. Django 前端页面用这个 token 请求 Flask 的预览地址。
  5. Flask 用 token 反向请求 Django 验证身份和权限。
  6. 验证通过后,Flask 读取文件并返回内容。

整个链路里,用户永远接触不到实际的磁盘路径,即使 token 泄露,也只在有效期内能访问某一指定文件,不能遍历其他文件。

5. 典型问题排查与避坑经验

5.1 并发借阅时的数据竞态问题

系统上线后的某一天,会出现一个看起来很诡异的问题:两个用户同时申请借阅同一份档案,管理员也同时审批通过了,但借出的时候一个成功一个失败。失败的那条提示“档案已借出”,管理员觉得莫名其妙,明明刚才查的时候还是在库的。

这就是典型的并发数据竞态问题。第一次出现的时候不要慌,检查代码里有没有做行锁。我之前也踩过这个坑,最初的借出实现是先查Archive状态,再更新状态,两步之间没有加锁。两台管理端浏览器几乎同时提交请求,两个进程同时查到了“在库”状态,然后都执行了更新,结果就是物理上档案已经被第一笔借出操作标记为“借出”了,第二笔操作覆盖了状态。

解决办法就是我上面代码里写的select_for_update(),把“查状态”和“改状态”放进同一个数据库事务里,并且锁住对应行记录。锁的顺序还要注意:先锁借阅记录,再锁档案记录,这个顺序在所有的借出、归还操作里要保持一致。

5.2 Flask 与 Django 路径拼接不一致的隐患

双框架项目里,最容易出问题的不是业务逻辑,而是两边对路径的理解不一致。Django 侧保存的是相对路径archive_files/contract/HT-2025-0001.pdf,Flask 侧拼接完整路径时用FILE_BASE_DIR + file_path。只要有一侧多加了斜杠或者路径拼接时用了 Windows 风格的分隔符,就会出现文件找不到。

这种问题在本地开发时往往发现不了,因为开发机和服务器操作系统可能不一样。排查的时候,先在 Flask 服务里加日志打印实际拼出来的完整路径,再和 Django 数据库里存的file_path对比,基本一次就能定位。

另外要提醒一个隐患,中文文件名。如果系统允许上传的文件名包含中文,在拼接 URL 或者做 HTTP 请求参数传递时,一定要做 URL 编码。用requests库时,中文参数要放在params里让它自动编码,不要自己手动拼 URL,否则经常会遇到编码不一致导致的 404。

5.3 Django Admin 中文显示与日期控件的小问题

Django Admin 本身很强大,但默认配置在中文环境下有几个小问题要处理。第一个是时间字段默认不带日期选择控件,要在ModelAdmin里显式配置date_hierarchy和list_filter,否则档案员录入借阅信息时手动敲日期很容易输错。

第二个是列表页默认显示的字段太少,管理员想快速看到档案的密级、状态、存放位置,都要在list_display里配置:

class ArchiveAdmin(admin.ModelAdmin): list_display = ('archive_no', 'title', 'archive_type', 'secret_level', 'location_code', 'status', 'created_at') list_filter = ('archive_type', 'secret_level', 'status') search_fields = ('archive_no', 'title') date_hierarchy = 'created_at'

这两项配置看起来不起眼,但对档案员来说,操作效率的提升非常明显。档案员每天可能要在后台处理几十条借阅申请,一个日期控件和一个筛选器,能省下大量手动查找的时间。

5.4 部署到服务器时的三个易错点

部署是另一个容易翻车的环节。归纳几个高频问题:

第一,静态文件收集。Django 的 Admin 自带样式文件,部署时一定要执行python manage.py collectstatic,把静态文件收集到指定目录,再让 Nginx 指向这个目录。忘记这一步的话,管理后台打开完全没有样式,页面全是裸 HTML。我见过不止一次有人在这个小问题上卡了半天。

第二,端口与反向代理配置。Django 用 Gunicorn 或 uWSGI 监听内网端口,Flask 服务监听另一个端口。Nginx 对外只暴露 80/443,根据路径把请求转发到对应的服务。配置 Nginx 转发时,要注意/api/preview/这类路径要精确匹配到 Flask 服务,不要和 Django 的 URL 规则冲突。

第三,服务进程的管理。Django 和 Flask 是两个独立进程,建议用 systemd 或进程管理器统一管理。否则服务器重启一次,两个服务有一个没起来,系统就处于半瘫痪状态。日志也要分开存,Django 的访问日志、Flask 的预览日志、系统的错误日志各归各,出了问题才好各自排查。

6. 一些实测下来的体会

档案室管理系统这个项目做完之后,我对 Django 和 Flask 双框架的搭配有了更实际的认识。两个框架共享一个数据库、通过 HTTP 接口互通,这个方案最核心的价值不是技术上的“优雅”,而是让每段代码都待在了最适合自己的位置。Django 处理复杂的业务状态流转和权限控制非常顺手,Flask 处理文件预览、格式转换这类轻量任务也相当轻快,两边互不拖累。

实际用下来也发现,这套架构最怕的不是框架本身,而是把两个服务之间的边界模糊化。Flask 服务一旦开始直接操作数据库表结构,或者 Django 开始直接暴露文件路径,整个架构的清晰度就崩溃了。守住“Django 管数据、Flask 管文件”这条线,系统就能保持好维护。

让我印象很深的还有一件事:档案管理系统的代码本身难度并不高,但业务细节非常琐碎。档号的防重、借阅状态的一致性、文件的权限管控、检索结果的密级过滤,每一个单独拿出来都不难,但合在一起就是一套完整且严密的管理体系。在这个过程中,真正花时间的不是写代码,而是把每一个业务规则的“为什么”想清楚,比如为什么要锁行、为什么文件不能放在静态目录、为什么检索前就要过滤密级。这些“为什么”想明白了,代码自然就写对了。

最后再分享一个小技巧:给档案表加一个简单的“最近访问记录”功能,不需要复杂设计,一张表记录哪个用户看了哪份档案、什么时间看的就够了。很多单位过一段时间就会来问“能不能查一下某某档案最近被谁看过”,这个功能在合规审查时价值极高,而且实现成本一天都用不到。做档案系统,永远不要只盯着业务主流程,那些不起眼的审计需求,关键时刻比主流程还重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询