☰
Python基于Django的档案宝微信小程序设计与开发实战
2026/10/6 3:15:22 网站建设 项目流程

做档案管理系统,说难不难,说简单也不简单。难在档案业务里全是“看似普通、实则规则复杂”的细节:密级控制、借阅审批、版本追溯、电子档案和纸质档案的对应关系,随便拎一条出来都能把开发折腾到半夜。简单则是因为这个领域的技术栈实在太成熟了——Python、Django、微信小程序三个关键词凑到一起,足够搭出一套能实际交付的“档案宝”系统。

这篇博文就围绕“python基于django的档案宝微信小程序设计开发实现”展开,完整记录我从零搭建这套系统的全过程。内容覆盖项目需求拆解、Django后端建模与API设计、小程序端页面开发、联调抓包、上线审核、常见报错排查等环节,适合正在做毕业设计、个人项目,或者刚接触 Django + 小程序开发、想找一个完整实战案例参考的开发者。我会把踩过的坑和实测可用的方案都写出来,代码片段可以直接抄。

1. 项目全景拆解:档案宝到底在解决什么问题

1.1 档案管理的真实痛点

我们常说的“档案”不是只有人事档案,还包括合同、图纸、项目文档、财务凭证、科研成果材料等。很多小团队和档案室至今还在用 Excel 管理,文件夹命名全靠人工约定,借阅要走线下审批,还回来之后放在哪个柜子全凭记忆。真正做过档案管理系统后会发现,核心痛点其实就三条:

第一是检索难。档案一旦超过几百份,按文件名搜索基本失灵,因为档案的查找维度很多:分类、密级、归档时间、责任人、关键词、编号区间。第二是权限乱。谁可以看全文,谁只能看目录,谁可以下载附件,这些规则如果靠人来执行,迟早出漏洞。第三是流转没有记录。借出去没还、谁借的、什么时候借的,如果不能追踪,档案实物很容易丢。

“档案宝”这个名字听起来简单,实际定位就是解决这三件事:档案的数字化归档、多维度检索、借阅流程管控。

1.2 功能范围界定与场景分析

在设计之前,必须先明确系统边界,不然做着做着就会失控。我这个项目的功能范围是这样界定的:

  • 档案录入与编辑:管理员在管理端(这里直接复用 Django Admin)录入档案基本信息,包括档号、标题、分类、密级、存放位置、附件文件等。
  • 终端检索:小程序端面向普通员工,按关键字、分类两种方式检索档案,只能看到自己有权限查看的档案目录和详情。
  • 借阅流程:小程序内提交借阅申请,管理员在线审批,通过后记录借出和归还时间。
  • 我的借阅:用户可以查看自己历史借阅记录和当前未归还档案。

没有做的部分也要说明白:我刻意不做全文检索和OCR识别,因为个人项目阶段引入 Elasticsearch 或 OCR 服务会显著增加部署成本。初期用 Django ORM 的 icontains 做关键字匹配已经完全够用,后续如果数据量上来再迁移到全文检索引擎也不迟。这种“先做减法、后续平滑升级”的思路,比一开始就追求大而全要靠谱得多。

2. 技术选型的取舍逻辑:为什么是Django而不是Node或Spring

2.1 后端为什么选择Django

后端框架可选范围其实很大,Node、Spring Boot、Flask 都能写。但针对“档案管理系统”这个场景,Django 有几个天然优势值得聊一聊。

第一个优势是自带 Admin 后台。档案管理系统必然需要一个管理端,用来做录入、审批、用户管理这些操作。如果自己从零写一套后台界面,工作量会非常大。Django Admin 只需要注册模型,就能获得一套完整的增删改查界面,表单校验、分页、搜索、筛选都自带。实测下来,一个档案模型注册进 Admin,大概只需要二十行代码,这个开发效率是其他框架比不了的。

第二个优势是 ORM 的查询表达能力。档案管理最核心的操作就是“查”,而 Django ORM 的 filter 链式调用在组合复杂查询条件时非常顺手。比如查询“2024年度归档的、密级为内部、标题包含合同”的档案,只需要一行链式表达式就能写清楚,这在原生 SQL 里要拼接很多条件,容易出错且不利于维护。

第三个优势是安全性。Django 默认内置 CSRF 防护、XSS 转义、SQL 注入防护,用户认证体系也是现成的。档案数据属于敏感数据,安全这块必须重视,选 Django 相当于一开始就站在了比较稳的起点上。

有人可能会问,为什么不用 Flask?Flask 足够轻量,但它的自由度过高,用户认证、Admin、ORM 这些都要自己选型组装。档案管理系统这种“业务规则多但都不是特别复杂”的项目,Django 的一体化设计能减少大量决策成本。

2.2 小程序端为什么不需要引入uniapp等重型框架

开发微信小程序,常见的选择是原生小程序、uni-app、Taro。这个项目最终选择了原生小程序开发,原因很现实:档案宝的核心功能就是档案列表、详情、借阅申请这几页,页面量不大,交互逻辑简单,原生框架写起来反而最直接。

uni-app 和多端框架的优势在于跨端复用,一套代码同时输出 App、H5、小程序。但这个项目只需要微信小程序一个端,而且同时用到了原生标签、自定义导航栏适配等能力。引入 uni-app 意味着多一层编译和兼容性开销,调试时遇到问题要排查框架层还是业务逻辑层,反而拖慢进度。

不过有一点要提醒:如果后续确定要扩展 App 端,原生小程序的代码迁移成本会比较高。我在设计时特意把小程序端的请求层做了统一封装,所有对后端 API 的调用都集中在 api.js 文件里。这样即使以后要用 uni-app 重写,也只需要替换请求封装这一层,页面逻辑可以复用大部分。这个“预留替换层”的设计思路,在技术选型有不确定性的时候非常适用。

2.3 数据库选型与存储设计

数据库我选的是 MySQL,因为档案系统后续可能要对接已有的组织架构数据,MySQL 在企业内部普及率最高,DBA 也好招。如果只是个人学习或原型验证,SQLite 也完全能跑。但要注意,SQLite 对并发写入支持较弱,小程序端如果同时多人提交借阅申请,可能出现数据库锁冲突,生产环境不建议用 SQLite 顶着。

文件存储方面,档案附件我用的是本地文件系统 + Django MEDIA 目录。生产环境更合理的做法是接入阿里云 OSS 或腾讯云 COS,把附件存到对象存储,数据库里只存 URL。但因为是小项目,本地存储最省事,只要做到按年月分目录存放,避免单目录文件过多即可。

3. 后端核心实现:Django模型设计、API与权限控制

3.1 数据模型设计:档案表、分类和借阅记录

后端第一步是建模。档案系统最重要的三张表是档案主表、分类表和借阅记录表。我直接给出核心模型代码,并解释每个字段的用意。

# archives/models.py from django.db import models from django.contrib.auth.models import User class ArchiveCategory(models.Model): """档案分类表""" name = models.CharField(max_length=50, unique=True, verbose_name="分类名称") remark = models.CharField(max_length=200, blank=True, verbose_name="备注") created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['id'] verbose_name = "档案分类" verbose_name_plural = verbose_name def __str__(self): return self.name class Archive(models.Model): """档案主表""" SECRET_LEVEL_CHOICES = [ ('public', '公开'), ('internal', '内部'), ('confidential', '保密'), ] archive_no = models.CharField(max_length=32, unique=True, verbose_name="档号") title = models.CharField(max_length=200, verbose_name="档案标题") category = models.ForeignKey(ArchiveCategory, on_delete=models.PROTECT, verbose_name="所属分类") secret_level = models.CharField(max_length=20, choices=SECRET_LEVEL_CHOICES, default='internal', verbose_name="密级") location = models.CharField(max_length=100, blank=True, verbose_name="存放位置") content = models.TextField(blank=True, verbose_name="内容摘要") file = models.FileField(upload_to='archive_files/%Y/%m/', blank=True, null=True, verbose_name="附件") creator = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="归档人") archived_at = models.DateField(verbose_name="归档日期") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-archived_at'] verbose_name = "档案" verbose_name_plural = verbose_name indexes = [ models.Index(fields=['archive_no']), models.Index(fields=['title']), ] def __str__(self): return f"{self.archive_no} - {self.title}" class BorrowRecord(models.Model): """借阅记录表""" STATUS_CHOICES = [ ('pending', '待审批'), ('approved', '已批准'), ('borrowed', '借出中'), ('returned', '已归还'), ('rejected', '已拒绝'), ] archive = models.ForeignKey(Archive, on_delete=models.PROTECT, verbose_name="借阅档案") applicant = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="借阅人") apply_reason = models.TextField(verbose_name="借阅事由") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") apply_time = models.DateTimeField(auto_now_add=True, verbose_name="申请时间") approver = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='approved_borrows', verbose_name="审批人") approve_time = models.DateTimeField(null=True, blank=True, verbose_name="审批时间") expect_return_date = models.DateField(verbose_name="预计归还日期") actual_return_date = models.DateField(null=True, blank=True, verbose_name="实际归还日期") remark = models.CharField(max_length=200, blank=True, verbose_name="备注") class Meta: ordering = ['-apply_time'] verbose_name = "借阅记录" verbose_name_plural = verbose_name def __str__(self): return f"{self.applicant.username} - {self.archive.archive_no}"

这里有三个设计细节值得单独说明。

第一个是ArchiveCategory的外键用了on_delete=models.PROTECT。为什么不用 CASCADE?因为档案分类被删除时,如果直接把档案也删掉,会造成不可逆的数据丢失。PROTECT 的含义是“保护”,只要该分类下还有档案,就不允许删除分类。在一些场景下可以考虑 SET_NULL,但前提是分类字段允许为空。档案分类是核心元数据,不允许为空,所以用 PROTECT 最合适。

第二个是archive_no设置唯一索引。档号是档案的身份证,录入后不可修改,检索也经常按档号精确查询,所以必须加 unique 约束并创建索引。

第三个是借阅状态机的设计。我用了五个状态:待审批、已批准、借出中、已归还、已拒绝。实际操作中,审批通过后管理员会线下给借阅人拿实物档案,此时状态从 approved 流转到 borrowed,归还后变成 returned。这个状态机虽然简单,但已经覆盖了档案借阅的全链路。注意,状态流转不要做成随意跳转,每一步都在视图函数里校验前置状态,防止用户通过接口直接改状态。

3.2 查询、删除与权限控制的实现细节

Django 执行查询离不开 ORM,热搜里提到“django执行查询-删除对象”,这里正好展开讲讲。档案的查询必须支持关键字、分类、密级组合过滤,参考代码如下:

# archives/views.py from django.db.models import Q from rest_framework.views import APIView from rest_framework.response import Response class ArchiveListView(APIView): """档案列表接口,支持关键字/分类/密级筛选,分页返回""" def get(self, request): keyword = request.query_params.get('keyword', '').strip() category_id = request.query_params.get('category_id', '') secret_level = request.query_params.get('secret_level', '') queryset = Archive.objects.select_related('category', 'creator').all() if keyword: queryset = queryset.filter( Q(title__icontains=keyword) | Q(archive_no__icontains=keyword) | Q(content__icontains=keyword) ) if category_id: queryset = queryset.filter(category_id=category_id) if secret_level: queryset = queryset.filter(secret_level=secret_level) # 分页,每页 10 条 page = int(request.query_params.get('page', 1)) page_size = 10 start = (page - 1) * page_size end = start + page_size total = queryset.count() items = queryset[start:end] data = [ { 'id': a.id, 'archive_no': a.archive_no, 'title': a.title, 'category': a.category.name, 'secret_level': a.secret_level, 'archived_at': a.archived_at, 'has_file': bool(a.file), } for a in items ] return Response({ 'total': total, 'page': page, 'has_more': end < total, 'items': data, })

这里有几个操作要点:

  • 关键字搜索用了Q对象组合三个字段的icontains,等价于 SQL 里的LIKE '%keyword%'。数据量小的时候没有问题,但如果档案量超过十万条,LIKE前导通配符会导致索引失效,届时需要换全文索引。我特意在项目里只保留了这个简单实现,但在代码注释里标注了后续优化方向。
  • select_related('category', 'creator')是 Django ORM 非常实用的查询优化手段。它通过 JOIN 把外键关联的数据一次性查出来,避免循环中逐条查数据库的 N+1 问题。档案列表页每页显示 10 条,如果不加这行,等于额外触发 10 次分类表和用户表的查询,数据再多点页面会明显变慢。
  • 分页我直接用了手动切片,没有用 Django 自带的 Paginator。原因很简单:小程序端只需要“上一页/下一页”这种滚动分页,不需要页码导航。手动切片配合has_more字段,接口返回体更轻,前端实现“加载更多”也更方便。

删除操作要单独强调。档案记录不建议物理删除,因为档案业务强调留痕。我的做法是在模型上增加is_active字段作为软删除标记,删除操作只更新字段值,不执行真正的delete()。查询时默认增加is_active=True过滤条件。这样既保留了数据审计线索,又能做到“看起来删了,实际上历史记录还在拉”。

# 软删除示例,不真正执行 delete() archive = Archive.objects.get(pk=archive_id) archive.is_active = False archive.save()

如果确实需要硬删除,ARCHIVE.objects.get(pk=id).delete()能直接删掉该记录,但务必注意级联行为。Django 默认的on_delete=models.CASCADE会导致关联的借阅记录一起被删除,这是非常危险的。我全项目的外键基本都没有用 CASCADE,就是为了防止误删。

3.3 Django REST Framework与接口认证方案

接口层我用的是 Django REST Framework,配合TokenAuthentication做小程序端的身份认证。Django 自带的 Session 认证不适合小程序,因为小程序没有 Cookie 管理的概念,用 Token 更直接。

生成 Token 的逻辑可以放在用户首次登录时:

# accounts/views.py from django.contrib.auth import authenticate from rest_framework.authtoken.models import Token from rest_framework.views import APIView from rest_framework.response import Response class LoginView(APIView): """小程序端登录接口:code(微信登录凭证) -> openid -> 本地用户 -> Token""" authentication_classes = [] # 登录接口本身不需要鉴权 permission_classes = [] def post(self, request): code = request.data.get('code') if not code: return Response({'error': '缺少code参数'}, status=400) # 这里要调用微信的 code2session 接口,用 code 换取 openid # 如果该 openid 在本地用户表中不存在,则自动创建用户 # 简化逻辑后,手动模拟 openid openid = 'mock_open_id_example' user, created = User.objects.get_or_create( username=openid, defaults={'first_name': '微信用户'} ) token, _ = Token.objects.get_or_create(user=user) return Response({ 'token': token.key, 'user_id': user.id, 'is_new_user': created, })

接口鉴权加权限控制的实现方式:

from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated class ArchiveDetailView(APIView): authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated] def get(self, request, archive_id): # 判断密级:public 所有登录用户可看,internal 需要是内部用户,confidential 需要在特定小组 archive = Archive.objects.get(pk=archive_id) return Response({ 'id': archive.id, 'title': archive.title, 'archive_no': archive.archive_no, 'category': archive.category.name, 'secret_level': archive.secret_level, 'location': archive.location, 'content': archive.content, 'file_url': self.request.build_absolute_uri(archive.file.url) if archive.file else None, 'creator': archive.creator.username, 'archived_at': archive.archived_at, })

密级控制这块,我的实现思路是:用户只读接口返回的 JSON 数据,如果档案是保密级别,前端详情页不做全文返回。实际项目里可以进一步扩展“密级 + 角色”的双层判断,写入权限校验函数。这里给一个简化版权限检查函数的示例:

def check_archive_permission(user, archive): """返回 True 表示允许访问,False 表示拒绝""" if archive.secret_level == 'public': return True if archive.secret_level == 'internal': return user.is_authenticated if archive.secret_level == 'confidential': # 这里假设只有 is_staff 用户才能看保密档案 return user.is_staff return False

不少新手一上来就纠结权限控制做得不够完美。我的建议是:先按“登录用户 + 密级”做一个版本上线跑通,后续再加“部门隔离”“借阅审批后临时解锁”这些高级规则。权限设计放进代码里,不要散落在视图各处。

4. 微信小程序端开发:从登录授权到列表分页的完整实现

4.1 页面结构与自定义导航栏适配

小程序端页面不多,但页面结构一定要提前规划好。我设计了四个页面:

  • 档案列表页(首页):搜索框 + 分类筛选栏 + 档案卡片列表,支持滚动加载
  • 档案详情页:展示档案元数据、内容摘要、附件预览/下载
  • 借阅申请页:选择档案、填写事由、选择预计归还日期,提交后生成借阅记录
  • 我的页面:展示当前用户的借阅记录列表和系统设置

关于“微信小程序顶部导航栏高度”这个话题,做过原生开发的人都知道这是必踩的坑。非自定义导航栏模式下,顶部导航栏的高度由系统决定,不同机型不一样。如果页面内容要贴合导航栏底部,需要动态获取状态栏高度和导航栏高度。我封装了一个工具函数:

// utils/system.js function getNavBarInfo() { const systemInfo = wx.getSystemInfoSync(); const statusBarHeight = systemInfo.statusBarHeight || 20; // 胶囊按钮位置信息,用来计算导航栏高度 const capsuleButtonInfo = wx.getMenuButtonBoundingClientRect(); const navBarHeight = (capsuleButtonInfo.top - statusBarHeight) * 2 + capsuleButtonInfo.height; return { statusBarHeight, navBarHeight, totalHeight: statusBarHeight + navBarHeight }; }

这个算法的核心逻辑是:导航栏高度近似等于“胶囊按钮顶部到状态栏底部的距离 × 2 + 胶囊按钮高度”。实测在 iPhone 和 Android 机型上表现都比较稳定。拿到导航栏高度后,页面的自定义头部组件只需设置style="padding-top: {{totalHeight}}px"即可。

4.2 登录授权与请求封装

小程序端的登录流程遵循微信官方的建议:wx.login()获取 code → 发送到后端换取 token → 之后所有请求在请求头带Authorization: Token xxx。我把请求层统一封装在api.js中,核心代码如下:

// utils/api.js const BASE_URL = 'https://your-server.example.com/api'; function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${path}`, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': `Token ${wx.getStorageSync('token')}` }, success(res) { // 如果 token 过期或未授权,跳转到登录页重新获取 code if (res.statusCode === 401) { wx.redirectTo({ url: '/pages/index/index' }); reject(res); return; } resolve(res.data); }, fail(err) { reject(err); } }); }); } module.exports = { get: (path, data) => request(path, 'GET', data), post: (path, data) => request(path, 'POST', data), };

封装请求层有两点好处:统一处理 Token 注入和 401 拦截响应,业务页面只需要关心自己的数据逻辑,不用每个页面都写一遍wx.request模板代码。

4.3 列表加载更多与下拉刷新

“微信小程序页面列表加载更多”是小程序开发高频需求。我的实现方案是:页面 onReachBottom 事件触发加载下一页,配合has_more字段决定是否继续请求,并使用 loading 状态防止重复请求。

页面核心示例:

// pages/archive-list/archive-list.js const api = require('../../utils/api.js'); Page({ data: { archives: [], page: 1, hasMore: true, loading: false, keyword: '', categoryId: '', }, onLoad() { this.loadArchives(true); }, async loadArchives(reset = false) { if (this.data.loading) return; if (!reset && !this.data.hasMore) return; this.setData({ loading: true }); const nextPage = reset ? 1 : this.data.page + 1; try { const data = await api.get('/archives/', { page: nextPage, keyword: this.data.keyword, category_id: this.data.categoryId, }); const list = reset ? data.items : this.data.archives.concat(data.items); this.setData({ archives: list, page: nextPage, hasMore: data.has_more, loading: false, }); } catch (e) { this.setData({ loading: false }); } }, onReachBottom() { this.loadArchives(false); }, onPullDownRefresh() { this.loadArchives(true).then(() => wx.stopPullDownRefresh()); }, onSearchInput(e) { this.setData({ keyword: e.detail.value }); }, onSearchConfirm() { this.setData({ archives: [], page: 1, hasMore: true }); this.loadArchives(true); }, onCategoryChange(e) { this.setData({ categoryId: e.currentTarget.dataset.id }); this.loadArchives(true); }, });

这里的代码包含一个容易忽略的逻辑点:关键字或分类变化后,分页页码必须重置为 1,同时清空已有列表,否则会出现“分页参数错位”的问题。另外,加载更多最好加上节流,我在onReachBottom里通过loading判断直接拦截重复触发,实测效果很稳。

5. 联调、抓包与发布:新手最容易卡住的几个环节

5.1 Charles抓包调试小程序请求

小程序开发和网页开发最大的区别在于调试链路。网页按 F12 就能看到所有请求,小程序只能在开发者工具的 Network 面板里看,但真机预览时看不到。如果遇到“开发工具里正常、真机上接口报错”的情况,就得上 Charles 抓包了。

Charles 的基本配置思路是:PC 和手机连接同一 Wi-Fi,手机代理指向 PC 的 IP:8888,然后安装 Charles 证书。关键步骤有三步:

第一步,打开 Proxy > SSL Proxying Settings,勾选 Enable SSL Proxying,添加需要抓包的主机名和端口。不添加 SSL 白名单的话,HTTPS 流量会显示成乱码。

第二步,手机端访问chls.pro/ssl下载安装证书。iOS 用户在安装证书后,还要进入“设置 > 通用 > 关于本机 > 证书信任设置”开启完全信任,否则抓包仍然看到的是加密流量。

第三步,小程序的 request 合法域名校验。要注意,微信开发者工具可以勾选“不校验合法域名”,但真机上必须配置合法域名。如果后端还没买域名,可以用局域网 IP 临时调试,小程序开发工具也要关闭校验合法域名,否则局域网接口也访问不了。

Charles 抓包最大的价值是能看到小程序端的请求头、请求体、响应体,跟后端接口文档逐一比对。我遇到过最典型的例子是:小程序端 Post 请求的 Content-Type 被自动设置成application/json,但后端 Django 的request.POST默认只解析表单格式,导致请求参数为空。这类问题不抓包根本看不出来,抓包后一眼就能定位。

5.2 审核与发布阶段容易踩的坑

小程序上线要经历微信公众平台的审核,档案类应用还涉及类目问题。这里说几个实际经验:

类目选择:个人主体的小程序能上的类目有限,如果档案宝需要企业管理类、政务服务类,个人主体大概率过不了,必须用企业主体注册。企业主体需要认证,费用每年一次,这也是一个实打实的成本考虑。

用户隐私保护指引:小程序涉及用户信息收集,必须配置用户隐私保护指引,否则审核会被驳回。常见驳回原因是“收集用户信息但未说明用途”。我的做法是在隐私保护指引里逐条列出收集的信息类型和使用场景,并保证后端只存储必要字段。

wx.login登录态有效期:小程序的 code 一次性有效,但后端 Token 我设置成了永不过期。从安全角度应该加有效期,实际上因为档案系统的用户量不大,Token 过期机制反而会造成用户频繁掉线。折中方案是设置 30 天过期,用户打开小程序如果 Token 失效,自动静默重新登录。

5.3 常见错误码速查表

开发过程中遇到了一些典型报错,整理成表格供参考:

报错/错误码出现场景原因分析解决方案
401 Unauthorized请求 archive 详情请求头没有带 Token 或 Token 失效检查 api.js 的 Authorization 头;重新登录获取新 Token
403 Forbidden提交借阅申请权限校验失败,用户不属于该档案的可见范围检查 CheckArchivePermission 逻辑里的密级判断
CSRF Failed: CSRF token missingDjango 返回 403Django 默认开启 CSRF 校验,API 接口没有处理 CSRF视图类加@csrf_exempt,或按 DRF 的认证方式走 Token,不走 CSRF
小程序错误码: 10002小程序上传文件文件大小超过 10MB 限制,或文件类型不在允许列表压缩图片,控制文件大小在 10MB 内
errno 600001使用 wx.request 请求不合法域名后端域名未在小程序后台配置合法域名登录小程序后台,配置 request 合法域名;开发调试时勾选不校验合法域名
Django: DoesNotExist查询不存在的档案 id前端传了错误的档案 id,或者数据已被软删除接口里先做 get_object_or_404,前端判断返回后再渲染详情

特别提一下 CSRF 的问题。Django 默认在模板渲染的表单中要求 CSRF Token,这是网页端保护机制。但小程序通过 TokenAuthentication 认证后,如果视图继承了 DRF 的APIView,DRF 默认使用了SessionAuthentication才会触发 CSRF。我的做法是统一使用 DRF 的TokenAuthentication,并且不需要@csrf_exempt,因为在 DRF 的APIView流程中,CSRF 只在 SessionAuthentication 下启用。如果读者用的是 Django 原生视图而非 DRF,那就要主动加@csrf_exempt。

6. 私下记录:做过档案类小程序后的几点体会

这个项目从设计到上线,前后改了三版接口。第一版按常规网页端思路把列表和详情分开返回,后来发现小程序端更希望列表自带摘要信息,减少一次请求。第二版加上了软删除和统一查询过滤,第三版才稳定下来。这个过程中的核心经验可以凝练成几句话。

第一点是“接口一定先定好返回结构,再写页面”。小程序端如果反复改接口字段,页面和渲染逻辑全部要跟着动,非常痛苦。我建议先用 Postman 或 ApiPost 把接口的关键字段定下来,前端页面按这个 mock 数据开发,后端同时按同一份结构实现,联调时阻力会小很多。

第二点是“档案管理系统不需要花哨功能,但一定要稳”。管理员最怕的是档案数据丢失或者权限错乱。所以我的项目里,上镜率最高其实是 Django Admin,而不是小程序端。小程序端面向普通员工,功能简单,但管理端要顺手、清晰、可追溯。建议后续扩展时优先完善管理端的批量导入、借阅过期提醒和数据导出功能。

第三点是“如果要做成真正的产品,可以往硬件和集成方向扩展”。比如给实体档案加二维码标签,小程序扫二维码直接显示档案信息;或者在档案室门口加蓝牙门禁,微信小程序通过蓝牙定位自动记录谁进入了档案室(这个场景正好是热搜词里提到的“微信小程序蓝牙定位”的落地方向)。再比如对接企业内部的企业微信或钉钉,把借阅审批流程嵌入办公平台。

最后再分享一个小技巧:Django 开发阶段的runserver自动重载很香,但真机调试小程序时,后端地址别用127.0.0.1,要写电脑的局域网 IP。同时记得关闭电脑防火墙或添加入站规则,否则手机访问不到 Django 进程。我一开始就被这个问题卡了半小时,改完防火墙马上就好了。这个细节虽然小,但它属于那种“文档里不会写、但实操必踩”的坑,遇到一次就能记住。

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

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

立即咨询