ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Flask和SQLite开发轻量公文签收系统,告别纸质台账

用Flask和SQLite开发轻量公文签收系统,告别纸质台账 简介一套基于经典ASP技术的简单公文签收系统面向ASP初学者及需要快速搭建轻量公文流转应用的小型组织。系统覆盖文件上传、流程定义、自动提醒、签收审批、状态追踪、版本控制与权限管理等核心环节结构简单却功能完整适合作为课程设计或毕业设计参考。压缩包共57个文件主体为27个ASP页面另含11个JPG和11个GIF图片素材、2个INC公共文件、1个PSD界面设计稿以及CSS样式、TXT说明、HTM帮助页、MDB数据库和BAK备份文件可支撑前后台完整运行。整个资源包仅196KB小巧便于部署。已有219人学习下载。代码注释清晰目录划分明确。通过源码可快速掌握文件上传、用户管理、部门管理、信息发布等功能的实现思路并借助说明文档、数据库备份和界面设计稿开展二次开发与界面定制。 办公室老张又抱着一摞盖着红章的公文挨个科室跑着催签收这是我决定动手做这套“简单的公文签收系统”的直接导火索。说是“简单”其实功能闭环一点都不少收文登记、线上签收、未签名单、台账查询全部跑在一个轻量级Web服务上。这套系统就是为综合办公室、单位内勤这类人员准备的不需要单独配服务器一台能跑Python的电脑就够代码量也不大却能彻底告别纸质签收单满天飞、Excel台账漏登记的日子。先说明一下我现在已经把这套系统实际用在了单位内网环境里从需求梳理、数据库设计到页面搭建、内网部署全程没有引入任何重量级框架。下面我把整个做项目的思路、核心代码和踩过的坑原原本本写出来想给你的部门或办公室也做一套类似工具的可以直接照着往下走。1. 项目背景与需求拆解1.1 公文签收为什么会成为问题大多数基层单位的公文签收表面上看着是“签个名就完事”但实际运行起来有几个很典型的痛点。第一是流转环节多一份文件从收发室登记经过办公室主任批办再到分管领导圈阅最后落到经办科室中间任何一个人出差或开会文件就被压在桌子上。第二是签收状态不透明领导问“某某文件发了没有”负责登记的人只能靠回忆或者把整个文件夹翻一遍。第三是台账和纸质签收单对不上每到月底统计办文量就得人工逐张翻签收单费时费力还容易漏。这些问题单靠Excel表其实能解决一部分但我试过之后发现不够用。原因是多人同时维护同一个Excel文件时版本冲突、内容被覆盖、表格锁死这些事反复发生而且系统不会主动提醒谁还没有签收。所以这个项目从一开始就不是“做一个花哨的系统”而是“把签收这个动作管理起来”。1.2 需求清单先列清楚再动手动手写代码之前我把需求归纳成了四个核心功能点。第一是收文登记办公室人员录入公文的标题、来文单位、收文日期、密级和办理时限第二是签收确认经办人在自己的待办列表里看到文件点击签收并填写签收意见第三是签收台账所有历史文件的签收状态、签收时间、签收意见可以随时查询并导出第四是催办提醒系统能列出三天以上未签收的文件方便办公室人员定向催办。围绕这四个功能我主动砍掉了一些暂时用不上的东西比如工作流审批、电子签章、移动端App。原因是这套系统的定位很明确做一个轻量、易维护、能解决实际流转问题的工具而不是建设一套完整OA。等业务量真的上来了再迁移到成熟的协同办公平台也不迟。需求收敛后整个系统的边界就非常清晰了后面开发的时候每一个页面、每一段代码都有明确的目的。2. 技术方案选型与整体设计2.1 为什么不选“大而全”的OA在动工之前我也认真考虑过直接用市面上的OA系统或者是钉钉、企业微信里的审批应用。但结合单位的实际情况这些方案有两个绕不开的问题。一是部署成本高OA系统通常需要单独的服务器和维护人员哪怕SaaS版本也要按人头收费为几十个人的签收需求开通一年的费用并不划算。二是流程太死板OA里的公文模块往往预设了复杂的审批链而我们的实际流程就是“登记-签收-查询”硬套通用流程反而增加了大家的学习成本。自己开发一套轻量系统最大的优势就是可以按需定制。界面上的每一个按钮都是我们业务里真实存在的动作没有冗余操作内网部署也完全不依赖外部网络。这种“定制感”带来的用户体验是通用软件给不了的。2.2 技术栈选定Flask SQLite Jinja2技术选型我特别保守最终选用的是Python Flask加SQLite配合Jinja2模板渲染页面。选择Flask的原因很直接它是Python生态里最简单易上手的Web框架单个文件就能写一个完整的应用路由和请求处理逻辑非常直观不需要像Django那样配置一整套项目骨架。SQLite则是为了零成本部署它就是一个文件数据库不需要额外安装数据库服务数据全部存放在一个文件里备份时把这个文件复制走就行。前端部分我没有采用前后端分离的方案也没有引入Vue或React而是直接用Jinja2模板在服务端渲染页面。这样整个项目只有十来个模板文件后端把签收状态、文件列表这些数据塞进模板用户打开页面就能看到完整内容。对于内网这种低并发场景这个组合的稳定性和维护成本都要远优于前后端分离架构。2.3 系统整体流程与模块划分系统的完整流程可以描述为登记员在“收文登记”页面填写公文基本信息并提交系统将公文存入数据库并生成一条唯一的公文编号相关人员登录进入系统后在主页面“待签收列表”中看到分配给本部门的文件点击“签收”按钮填写签收意见系统记录签收人、签收时间管理员进入“签收台账”页面可以按日期、部门、签收状态筛选所有文件的流转情况同时看到一个“未签收清单”用来做催办。整个应用从代码层面划分成了三个模块一是models.py负责数据库表结构和初始化的逻辑二是app.py作为主程序包含所有页面路由和业务处理逻辑三是templates目录存放页面模板文件。三个模块各司其职哪怕是我这种习惯边写边改的人也能随时定位问题出在哪个环节。3. 数据库设计与核心功能实现3.1 表结构设计够用又不啰嗦数据库设计是整个系统的基础我在这一块没有过度设计只用到了三张表。第一张users用户表很简单字段是用户ID、用户名、密码哈希、所属部门和角色。其中角色只区分“admin”和“user”两种admin可以登记文件和查看全部台账普通用户只能签收和查看自己相关的文件。密码没有存明文用的是werkzeug自带的密码哈希函数别人即使拿到数据库文件也看不到原始密码。第二张documents公文表是核心表字段包括公文编号、文件标题、来文单位、收文日期、办理时限、密级、签收状态、创建人和创建时间。密级字段我没有做复杂的控制在这里只是作为一个信息项展示毕竟真正涉密的文件根本不应该走这套系统。签收状态我用了整数来存0代表未签收1代表已签收这样后续做统计时非常方便。第三张sign_logs签收记录表负责记录每一次签收操作字段有签收记录ID、公文ID、签收人、签收时间、签收意见。之所以单独建一张表而不是直接往公文表里写签收人是考虑到同一份文件可能被多个部门签收一对多关系用独立的日志表更合理。三张表建好之后配合外键关联就能覆盖所有核心业务场景了。建表语句我放在了init_db.py脚本里关键代码如下import sqlite3 conn sqlite3.connect(sign_system.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, department TEXT NOT NULL, role TEXT NOT NULL DEFAULT user ) ) cursor.execute( CREATE TABLE IF NOT EXISTS documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, doc_no TEXT UNIQUE NOT NULL, title TEXT NOT NULL, sender TEXT NOT NULL, receive_date TEXT NOT NULL, deadline TEXT, secret_level TEXT DEFAULT 普通, sign_status INTEGER DEFAULT 0, creator TEXT NOT NULL, create_time TEXT NOT NULL ) ) cursor.execute( CREATE TABLE IF NOT EXISTS sign_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, doc_id INTEGER NOT NULL, signer TEXT NOT NULL, sign_time TEXT NOT NULL, comment TEXT, FOREIGN KEY (doc_id) REFERENCES documents(id) ) ) conn.commit() conn.close()3.2 签收流程的核心逻辑签收操作是整个系统的核心交互动作我把它实现成一个POST请求。用户在待签收列表中看到公文后点击“签收”前端弹出一个对话框要求填写签收意见后端收到请求后做两步事务性操作第一步把documents表里对应记录的sign_status更新为1第二步往sign_logs表里插入一条签收日志记录谁在什么时间签收的。这里有一个值得分享的细节我并没有限制一份文件只能被一个人签收而是允许多个部门分别签收。比如一份需要办公室和财务科共同落实的文件两个部门的人在各自登录后都能看到并签收台账上会显示两条签收记录。这样设计更符合实际业务一份文件确实可能对应多个执行主体。但如果你们的业务流程是固定的单人签收只需要在写SQL时加上“当前公文已是已签收状态普通用户不再显示该条记录”的过滤条件即可。签收路由示例代码如下app.route(/sign/int:doc_id, methods[POST]) def sign_document(doc_id): if not session.get(user): return redirect(url_for(login)) signer session[user] comment request.form.get(comment, ).strip() now datetime.now().strftime(%Y-%m-%d %H:%M:%S) conn get_db() conn.execute( UPDATE documents SET sign_status 1 WHERE id ?, (doc_id,) ) conn.execute( INSERT INTO sign_logs (doc_id, signer, sign_time, comment) VALUES (?, ?, ?, ?), (doc_id, signer, now, comment), ) conn.commit() flash(签收成功, success) return redirect(url_for(todo_list))这一步做完数据模型上已经形成了完整闭环登记产生公文签收产生日志日志支撑台账查询。3.3 催办提醒与台账统计催办功能是这个系统真正给办公室人员减负的地方。以前催办靠人工翻本子现在系统自动筛数据。我先在documents表里取了sign_status 0的记录然后根据创建时间计算已经过去的天数把超过三天的文件单独列成清单。页面上用不同颜色标注黄色代表超过三天红色代表超过五天办公室人员一眼就能看出哪些文件急需跟催。台账页则提供多维度的查询支持按收文日期范围、来文单位、标题关键词、签收状态组合筛选。所有查询结果都是基于SQL拼接实现的虽然技术含量不高但胜在简单可靠。查询结果支持导出CSV用Excel打开就是一张现成的报表月底统计办文量时免去了重新汇总的麻烦。筛选逻辑的一个核心SQL片段如下query SELECT * FROM documents WHERE 11 params [] if keyword: query AND title LIKE ? params.append(f%{keyword}%) if status: query AND sign_status ? params.append(int(status)) if start_date: query AND receive_date ? params.append(start_date) if end_date: query AND receive_date ? params.append(end_date) rows conn.execute(query ORDER BY receive_date DESC, params).fetchall()4. 实操搭建过程实录4.1 环境准备与项目初始化如果你准备在自己的电脑上复现这套系统需要的只有Python 3.9以上版本和pip工具。创建虚拟环境这一步别偷懒它能避免你的系统依赖被项目搞乱。初始化完成后安装Flask、Werkzeug两个核心包就行不需要额外的数据库驱动因为SQLite是Python标准库自带的能力。初始化项目时我的目录结构是这样的sign_system/ ├── app.py ├── init_db.py ├── models.py ├── requirements.txt ├── templates/ │ ├── base.html │ ├── login.html │ ├── index.html │ ├── todo_list.html │ ├── register_doc.html │ └── ledger.html └── instance/ └── sign_system.dbrequirements.txt只有三行Flask、Werkzeug和itsdangerousFlask的依赖会自动安装。项目的生命周期里大概率不会遇到依赖冲突这点是选择轻量框架带来的安心感。在数据库初始化时我预先插入了一个管理员账号用户名是admin初始密码是admin123登录后第一件事就是把密码改掉。这个初始账号的插入逻辑写在init_db.py里和建表语句放在一起方便新环境重新初始化。4.2 后端路由与前端页面的联动用户通过浏览器看到的每一个页面背后都对应一个路由函数。我把主要路由归纳成了五类登录注册、首页统计、待办列表、收文登记、签收台账。每一类路由内部负责GET请求的通常是渲染模板负责POST请求的通常是数据处理。这种“GET显示页面POST处理操作”的约定让代码结构非常清晰哪怕隔了一个月再来看也能立刻想起来某段逻辑是干什么的。登录状态我用Flask自带的session来维持用户密码哈希验证通过后把用户名和角色写入session之后的每一个请求在进入具体处理逻辑前都会做一个登录校验未登录的用户一律重定向到登录页面。为了不让每个视图函数里都重复写登录校验代码我写了一个login_required装饰器这也是Flask里最常见的做法。页面模板之间通过base.html继承保持了统一的视觉风格和导航栏左侧菜单是待签收列表、收文登记、签收台账、系统管理四项顶部显示当前登录用户和退出按钮。因为目标用户是办公室的同事所以我没有在样式上投入太多精力只引入了一个简易的CSS框架界面干净、按钮够大、字够清楚就行。后端在响应请求时把动态数据注入模板前端通过Jinja2的语法进行循环渲染例如待签收列表页的核心模板片段如下{% for doc in pending_docs %} tr td{{ doc.doc_no }}/td td{{ doc.title }}/td td{{ doc.sender }}/td td{{ doc.receive_date }}/td td button onclickopenSignModal({{ doc.id }})签收/button /td /tr {% endfor %}4.3 内网部署与日常维护部署阶段我采用的是最朴素的方式在一台Windows内网服务器上安装Python环境把整个项目文件夹复制过去运行waitress-serve --port8080 app:app命令启动服务。Waitress是Windows平台上比较靠谱的纯Python WSGI服务器比Flask自带的开发服务器稳定得多能扛住几十个人同时在线访问如果只是单位内网使用完全足够了。数据库文件的日常维护主要是备份我写了一个备份脚本每天凌晨通过Windows计划任务执行把sign_system.db文件复制到另一块磁盘上文件名追加日期。这个动作成本几乎为零却是整个系统最值得保留的一道保险。万一某人误操作删了数据至少能把昨天的数据找回来损失控制在一天以内。5. 常见问题与排查技巧实录5.1 中文乱码问题这套系统在开发过程中遇到最多的其实是编码问题。Windows内网环境的电脑默认编码可能是GBK而Flask和SQLite默认按UTF-8处理这会导致页面上出现乱码。排查思路很直接先在浏览器页面右键查看字符编码再检查数据库连接参数是否指定了UTF-8。我的解决办法是在app.py里统一加上响应头的字符集设置并保证HTML模板里有meta charsetutf-8之后乱码问题就再没出现过。还遇到过一种更隐蔽的情况同事在Excel里复制公文标题粘贴到表单里带进来一些特殊字符或不可见空格入库后在页面上显示正常但导出CSV时总多出奇怪符号。后来我在表单提交的入口把字符串统一做了清洗把空白字符和非法字符过滤掉这个问题才算彻底解决。处理用户输入时宁可严格一点也不要太宽松。5.2 SQLite并发写入报错SQLite虽然是文件数据库但在多人同时签收这种“写多读少”的场景下偶尔会报“database is locked”。项目初期出现过几次排查后确认是多个并发连接同时写入导致的锁冲突。解决办法有两步第一步是在每次数据库连接建立时打开WAL模式这个模式能显著提升读写并发性能命令是PRAGMA journal_modeWAL;。第二步是所有写操作尽量在短时间内完成并立即关闭连接避免长时间占用数据库锁。WAL模式开启之后数据库目录下会出现两个额外的文件-wal和-shm这是正常现象。每次备份时要注意把这三个文件一起复制或者先执行一次checkpoint操作把WAL内容合并进主数据库否则备份文件可能不完整。这个小细节我一开始没注意测试恢复时才发现备份文件里缺了最后几笔数据。5.3 误操作与权限管理的取舍实际运行几个月后我收到过最多的反馈不是功能不够而是“我不小心点错了”。比如说登记员新建公文时标题多打了一个字或者同事签收时填写的意见写错了。这类误操作在纸质流程里很容易处理划掉重签就行但在系统里就涉及到已有数据的修改权限。我给普通用户分配了“已签收公文撤销重签”的权限前提是签收时间在当天超过当天就无法撤回。管理员则可以对所有记录进行修改和删除操作。这样既避免了误操作需要找管理员解锁的麻烦又保证了签收日志的严肃性。权限控制的核心原则是“让数据可追溯不是让所有人随便改”系统的任何操作都会在签收记录表里留下操作痕迹所以权限放开一点问题也不大。5.4 浏览器兼容与使用习惯最后想分享一个容易被忽略的问题单位内网环境里同事们用的浏览器五花八门有老IE内核的、各种国产浏览器兼容模式的也有新版Chrome和Edge。早期我在前端用一个比较现代的日期选择组件结果有同事反馈页面打不开或按钮点不动排查后发现是浏览器版本太老不兼容。解决方法有两个一个是把前端代码尽量写得“保守”不用太新的CSS和JavaScript语法另一个是在系统首页加了个浏览器检测的小提示检测到IE内核时就显示“建议使用Edge浏览器访问系统功能才能完整展示”。两个办法配合下来兼容性问题少了很多。做这类内部工具时永远不要高估同事的浏览器版本这是很现实的经验。写在最后的一点心得这套公文签收系统的开发过程并不复杂满打满算实际编码时间也就两三天但它解决了许多真实运转中的麻烦。我最大的体会是做内部管理工具越简单越好维护越好不要一开始就想着架构多先进、功能多齐全先把“登记、签收、台账、催办”这四个核心动作做顺系统就已经成功了一大半。如果你也想给单位或部门做一套类似的系统我个人建议从Excel台账和纸质签收的痛苦场景出发梳理出最痛的那几个点再动手写代码。另外系统上线之后一定要组织一次简单的使用培训哪怕只是当面演示十分钟都能避免后续大量的答疑和误操作返工。最后再分享一个小技巧给系统里的每一个关键操作都加上操作日志虽然前期看起来多写了几个字段但真正出现问题回溯的时候你就会庆幸当时多留了这一手。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进