从零搭建”一物一码”合格证查询系统——Django + DRF 后端架构详解
系列文章(2/5) :本篇将深入拆解后端核心架构,涵盖环境配置、数据模型、视图层、权限体系、中间件审计、JWT 认证与服务层设计。如果你还没阅读第一篇系统总览,建议先回顾后再继续。
一、Django 配置与环境管理 在实际项目中,开发环境和生产环境的配置往往差异巨大。我们使用 django-environ 配合 APP_ENV 环境变量,实现了一套零硬编码 的配置方案。
1.1 基于 APP_ENV 的多环境加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import osimport environfrom pathlib import PathBASE_DIR = Path(__file__).resolve().parent.parent env = environ.Env( DEBUG=(bool , False ), ) APP_ENV = os.environ.get("APP_ENV" , "development" ) env_file = BASE_DIR / f".env.{APP_ENV} " if env_file.is_file(): environ.Env.read_env(str (env_file))
这样做的好处是:.env.development 和 .env.production 可以同时存在于项目中(开发用前者,CI/CD 注入后者),而 settings.py 本身不需要任何 if-else 分支。
1.2 关键配置项 1 2 3 4 5 SECRET_KEY = env("SECRET_KEY" ) DEBUG = env("DEBUG" ) ALLOWED_HOSTS = env.list ("ALLOWED_HOSTS" , default=["localhost" , "127.0.0.1" ]) BASE_URL = env("BASE_URL" , default="http://localhost:8000" )
1.3 生产环境安全加固 当 APP_ENV=production 时,我们额外启用一系列安全中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if APP_ENV == "production" : SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_CONTENT_TYPE_NOSNIFF = True
小贴士 :SECURE_HSTS_SECONDS 一旦设置,浏览器会在指定时间内强制 HTTPS。首次部署建议先设为较短时间(如 3600),验证无误后再改为一年。
二、数据模型设计 模型层是整个系统的基石。在”一物一码”场景中,一张合格证(Certificate)绑定一批货物(Cargo),货物又关联一个产品(Product)。我们在模型层做了大量数据完整性保护 。
2.1 FROZEN_FIELDS 不可变守卫 已发布的合格证具有法律效力,核心字段绝不能被篡改。我们在 save() 方法中实现了字段冻结机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Certificate (models.Model): FROZEN_FIELDS = { "cert_number" , "inspection_org" , "inspection_date" , "standard_code" , "product" , } cert_number = models.CharField("证书编号" , max_length=64 , unique=True ) publish_status = models.CharField( "发布状态" , max_length=16 , choices=[("draft" , "草稿" ), ("published" , "已发布" ), ("revoked" , "已吊销" )], default="draft" , ) def save (self, *args, **kwargs ): if self .pk: old = type (self ).objects.filter (pk=self .pk).first() if old and old.publish_status == "published" : for f in self .FROZEN_FIELDS: if getattr (old, f) != getattr (self , f): raise PermissionError( f"已发布证书的「{f} 」字段不可修改" ) super ().save(*args, **kwargs)
这种”模型层拦截”比在视图层做校验更可靠——无论是 API 调用、管理后台操作还是 manage.py shell 手动修改,都会被拦截。
2.2 部分唯一约束(Partial Unique Constraint) 同一批货物只能有一张有效的 已发布证书,但草稿和已吊销的不受限制:
1 2 3 4 5 6 7 8 9 class Meta : constraints = [ models.UniqueConstraint( fields=["cargo" ], condition=models.Q(publish_status="published" ), name="uniq_active_published_cert_per_cargo" , ), ]
这利用了 PostgreSQL 的 Partial Index 特性。当你尝试为同一批货物发布第二张证书时,数据库层面就会直接拒绝——连 ORM 都绕不过去。
2.3 自动生成 ID 系统中有三类需要自动生成的标识符,各有不同的生成策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import uuidimport secretsfrom datetime import datedef _new_token (): """扫码查询用的随机令牌,URL 安全、32 字符""" return secrets.token_urlsafe(24 ) def _new_cargo_code (): """货物批次号:日期前缀 + 8 位随机串,便于人工识读""" prefix = date.today().strftime("%Y%m%d" ) suffix = uuid.uuid4().hex [:8 ].upper() return f"CG-{prefix} -{suffix} " def _new_cert_number (): """证书编号:年份 + 12 位随机串,全局唯一""" year = date.today().strftime("%Y" ) rand = uuid.uuid4().hex [:12 ].upper() return f"CERT-{year} -{rand} "
使用 secrets.token_urlsafe 生成扫码令牌是有意为之——相比 uuid4,它提供了密码学安全的随机性,防止令牌被暴力枚举。
2.4 仅追加模型(Append-Only) 操作日志和审计记录是系统的”黑匣子”,一旦写入就不能修改或删除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class EditLog (models.Model): """编辑日志:只能创建,不能修改,不能删除""" certificate = models.ForeignKey(Certificate, on_delete=models.CASCADE) changed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True ) changed_at = models.DateTimeField(auto_now_add=True ) field_name = models.CharField(max_length=64 ) old_value = models.TextField(blank=True ) new_value = models.TextField(blank=True ) def save (self, *args, **kwargs ): if self .pk: raise PermissionError("编辑日志不可修改" ) super ().save(*args, **kwargs) def delete (self, *args, **kwargs ): raise PermissionError("编辑日志不可删除" ) class AuditEntry (models.Model): """审计追踪:所有关键操作的不可篡改记录""" action = models.CharField("操作类型" , max_length=32 ) actor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True ) ip_address = models.GenericIPAddressField("IP 地址" , null=True ) timestamp = models.DateTimeField(auto_now_add=True ) detail = models.JSONField("详情" , default=dict ) def save (self, *args, **kwargs ): if self .pk: raise PermissionError("审计记录不可修改" ) super ().save(*args, **kwargs) def delete (self, *args, **kwargs ): raise PermissionError("审计记录不可删除" )
同样的手法——在 ORM 层强制执行业务规则,让上层代码无法”绕道”。
三、DRF 视图层架构 视图层采用 Django REST Framework 的 ViewSet 模式,将 CRUD 和自定义操作统一归集到一个类中。
3.1 ViewSet 基本结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from rest_framework import viewsets, statusfrom rest_framework.decorators import actionfrom rest_framework.response import Responseclass CertificateViewSet (viewsets.ModelViewSet): """合格证 CRUD + 业务操作""" queryset = Certificate.objects.select_related("product" , "cargo" ) serializer_class = CertificateSerializer permission_classes = [IsAuthenticated, IsAdminOrReadOnly] def get_queryset (self ): """普通操作员只能看到自己创建的证书""" qs = super ().get_queryset() if not is_admin(self .request.user): qs = qs.filter (created_by=self .request.user) return qs
3.2 自定义批量操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class CargoViewSet (viewsets.ModelViewSet): @action(detail=False , methods=["post" ], url_path="batch-create" ) def batch_create (self, request ): """批量创建货物批次,一次最多 500 条""" items = request.data.get("items" , []) if len (items) > 500 : return Response( {"error" : "单次批量不超过 500 条" }, status=status.HTTP_400_BAD_REQUEST, ) created = cert_service.batch_create_cargo(items, actor=request.user) return Response({"created" : len (created)}, status=status.HTTP_201_CREATED) @action(detail=False , methods=["post" ], url_path="batch-bind-cert" ) def batch_bind_cert (self, request ): """批量绑定证书到货物""" bindings = request.data.get("bindings" , []) results = cert_service.batch_bind_certificate(bindings, actor=request.user) return Response(results) @action(detail=False , methods=["post" ], url_path="batch-delete" ) def batch_delete (self, request ): """批量删除草稿状态的货物(已发布的不可删)""" ids = request.data.get("ids" , []) deleted = cert_service.batch_delete_cargo(ids, actor=request.user) return Response({"deleted" : deleted}) @action(detail=False , methods=["get" ], url_path="export-xlsx" ) def export_xlsx (self, request ): """导出 Excel 报表""" buffer = cert_service.export_cargo_xlsx(request.user) response = HttpResponse( buffer.getvalue(), content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" , ) response["Content-Disposition" ] = 'attachment; filename="cargo_export.xlsx"' return response
3.3 审核流操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class CertificateViewSet (viewsets.ModelViewSet): @action(detail=True , methods=["post" ], url_path="submit-review" ) def submit_review (self, request, pk=None ): """提交审核:草稿 → 待审核""" cert = self .get_object() cert_service.submit_for_review(cert, actor=request.user) return Response({"status" : "pending_review" }) @action(detail=True , methods=["post" ] ) def approve (self, request, pk=None ): """审核通过:待审核 → 已发布(含双控检查)""" cert = self .get_object() self ._check_dual_control(cert, request.user) cert_service.approve(cert, actor=request.user) return Response({"status" : "published" }) @action(detail=True , methods=["post" ] ) def reject (self, request, pk=None ): """审核驳回:待审核 → 草稿""" cert = self .get_object() reason = request.data.get("reason" , "" ) cert_service.reject(cert, actor=request.user, reason=reason) return Response({"status" : "draft" }) @action(detail=True , methods=["post" ] ) def revoke (self, request, pk=None ): """吊销证书:已发布 → 已吊销""" cert = self .get_object() reason = request.data.get("reason" , "" ) cert_service.revoke(cert, actor=request.user, reason=reason) return Response({"status" : "revoked" }) @action(detail=True , methods=["post" ] ) def reissue (self, request, pk=None ): """补发证书:基于已吊销的证书创建新草稿""" cert = self .get_object() new_cert = cert_service.reissue(cert, actor=request.user) return Response(CertificateSerializer(new_cert).data)
3.4 公开扫码查询端点 这是面向消费者的核心接口——扫描二维码后调用此端点验证合格证真伪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from rest_framework.throttling import AnonRateThrottleclass ScanRateThrottle (AnonRateThrottle ): rate = "30/minute" class PublicCertView (APIView ): """扫码查询——无需登录,面向消费者""" authentication_classes = [] permission_classes = [AllowAny] throttle_classes = [ScanRateThrottle] def get (self, request, token ): cargo = get_object_or_404(Cargo, token=token) cert = cargo.certificates.filter (publish_status="published" ).first() if not cert: return Response( {"error" : "未找到有效合格证" }, status=status.HTTP_404_NOT_FOUND, ) result = { "cert_number" : cert.cert_number, "product_name" : cert.product.name, "inspection_org" : cert.inspection_org, "inspection_date" : cert.inspection_date, "standard_code" : cert.standard_code, } scan_count = ScanLog.objects.filter ( token=token, scanned_at__gte=now() - timedelta(hours=24 ), ).count() if scan_count > 50 : result["risk_flag" ] = "该码 24 小时内被扫描超过 50 次,请注意辨别" if cert.expiry_date and cert.expiry_date < date.today(): result["expiry_alert" ] = "该合格证已过期" if cert.signature: is_valid = signing.verify_certificate(cert) result["integrity" ] = "valid" if is_valid else "tampered" ScanLog.objects.create( token=token, ip_address=get_client_ip(request), user_agent=request.META.get("HTTP_USER_AGENT" , "" ), ) return Response(result)
3.5 认证级联(Authentication Cascade) 后台管理接口需要支持多种客户端访问场景,我们实现了三级认证回退:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def _try_authenticate (request ): """ 认证级联:按优先级依次尝试三种认证方式 1. JWT Header — 标准 SPA 前端(Authorization: Bearer xxx) 2. Django Session — 管理后台(admin站点) 3. Cookie JWT — 跨端口开发时的 Cookie 回退方案 """ from rest_framework_simplejwt.authentication import JWTAuthentication jwt_auth = JWTAuthentication() try : result = jwt_auth.authenticate(request) if result: return result[0 ] except Exception: pass if hasattr (request, "user" ) and request.user.is_authenticated: return request.user token = request.COOKIES.get("access_token" ) if token: try : validated = jwt_auth.get_validated_token(token) return jwt_auth.get_user(validated) except Exception: pass return None
3.6 双控校验(Dual Control) 合格证的”创建”和”审批”必须由不同的人完成,防止一人同时扮演两个角色:
1 2 3 4 5 6 7 8 9 def _check_dual_control (self, cert, approver ): """ 双控检查:审批人 ≠ 创建人 防止同一人既创建又审批,确保四眼原则(Four-Eyes Principle) """ if cert.created_by == approver: raise PermissionDenied( "创建人不能审批自己创建的合格证,请交由其他管理员处理" )
四、三角色权限体系 系统采用三级角色设计,既简洁又够用:
角色
判定条件
核心权限
superadmin
is_superuser=True
所有操作 + 用户管理
admin
属于 "admin" 分组
证书审批、吊销、补发
operator
任何已认证用户
创建草稿、提交审核
4.1 角色判定函数 1 2 3 4 5 6 7 8 9 def is_admin (user ) -> bool : """判断用户是否为管理员(含超级管理员)""" return user.is_authenticated and ( user.is_superuser or user.groups.filter (name="admin" ).exists() ) def is_superadmin (user ) -> bool : """判断用户是否为超级管理员""" return user.is_authenticated and user.is_superuser
4.2 权限类实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from rest_framework.permissions import BasePermission, SAFE_METHODSclass IsSuperAdmin (BasePermission ): """仅超级管理员可访问""" def has_permission (self, request, view ): return is_superadmin(request.user) class IsAdmin (BasePermission ): """管理员及以上可访问""" def has_permission (self, request, view ): return is_admin(request.user) class IsAdminOrReadOnly (BasePermission ): """管理员可写,其他人只读""" def has_permission (self, request, view ): if request.method in SAFE_METHODS: return request.user.is_authenticated return is_admin(request.user) class IsAdminAction (BasePermission ): """用于装饰特定 action,非管理员直接拒绝""" def has_permission (self, request, view ): admin_actions = {"approve" , "reject" , "revoke" , "reissue" } if view.action in admin_actions: return is_admin(request.user) return True
4.3 权限映射关系 1 2 3 4 5 6 7 8 9 POST /api/certs/ → operator+ (创建草稿) GET /api/certs/ → operator+ (查看列表) POST /api/certs/{id}/submit-review → operator+ (提交审核) POST /api/certs/{id}/approve → admin+ (审核通过) POST /api/certs/{id}/reject → admin+ (审核驳回) POST /api/certs/{id}/revoke → admin+ (吊销证书) POST /api/certs/{id}/reissue → admin+ (补发证书) GET /api/users/ → superadmin (用户管理) GET /api/scan/{token} → 公开 (扫码查询)
五、中间件与审计 5.1 CurrentActorMiddleware Django 的 ORM 信号(post_save, post_delete)触发时没有 request 上下文 。但审计记录需要知道”谁做的”和”从哪里来的”。解决方案是使用 threading.local() 在中间件中存储当前请求者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import threadingfrom django.utils.deprecation import MiddlewareMixin_local = threading.local() def get_client_ip (request ): """从请求头中提取真实客户端 IP(支持反向代理)""" x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR" ) if x_forwarded_for: return x_forwarded_for.split("," )[0 ].strip() return request.META.get("REMOTE_ADDR" ) class CurrentActorMiddleware (MiddlewareMixin ): """ 在请求生命周期内,将当前用户和 IP 存入线程局部变量。 这样 ORM 信号处理器就能通过 get_current_actor() 获取操作者信息。 """ def __call__ (self, request ): if hasattr (request, "user" ) and request.user.is_authenticated: _local.actor = request.user else : _local.actor = None _local.ip = get_client_ip(request) try : response = self .get_response(request) return response finally : _local.actor = None _local.ip = None def get_current_actor (): """供 ORM 信号处理器调用,获取当前操作者""" return getattr (_local, "actor" , None ) def get_current_ip (): """供 ORM 信号处理器调用,获取当前请求 IP""" return getattr (_local, "ip" , None )
5.2 信号处理器中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from django.db.models.signals import post_savefrom django.dispatch import receiver@receiver(post_save, sender=Certificate ) def log_certificate_change (sender, instance, created, **kwargs ): """证书变更时自动创建审计记录""" actor = get_current_actor() ip = get_current_ip() AuditEntry.objects.create( action="cert_created" if created else "cert_updated" , actor=actor, ip_address=ip, detail={ "cert_id" : instance.pk, "cert_number" : instance.cert_number, "status" : instance.publish_status, }, )
为什么不直接在视图里写审计? 因为数据变更可能发生在任何地方——视图、管理命令、Celery 任务、甚至 manage.py shell。信号 + 中间件的组合确保了无死角审计 。
六、JWT 认证与 Cookie 认证 6.1 SimpleJWT 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from datetime import timedeltaSIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME" : timedelta(minutes=60 ), "REFRESH_TOKEN_LIFETIME" : timedelta(days=7 ), "ROTATE_REFRESH_TOKENS" : True , "BLACKLIST_AFTER_ROTATION" : True , "ALGORITHM" : "HS256" , "SIGNING_KEY" : SECRET_KEY, }
6.2 Token 生命周期图 1 2 3 4 5 6 7 8 9 10 11 用户登录 │ ├─ 颁发 Access Token ──── 有效 60 分钟 ──── 过期 │ │ └─ 颁发 Refresh Token ──── 有效 7 天 用 Refresh 换新 Token │ ┌───────┴───────┐ │ 新 Access (60m)│ │ 新 Refresh (7d)│ └────────────────┘ 旧 Refresh → 黑名单
6.3 CookieJWTAuthentication 在开发环境中,前端(端口 3000)和后端(端口 8000)跨端口部署,Authorization 头在某些场景下不方便传递。我们实现了 Cookie 回退方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from rest_framework_simplejwt.authentication import JWTAuthenticationclass CookieJWTAuthentication (JWTAuthentication ): """ 从 Cookie 中读取 JWT,作为 Header 认证的补充。 主要用于: 1. 开发环境前后端跨端口 2. 服务端渲染(SSR)页面 """ def authenticate (self, request ): header = self .get_header(request) if header: return super ().authenticate(request) raw_token = request.COOKIES.get("access_token" ) if raw_token is None : return None validated_token = self .get_validated_token(raw_token) return self .get_user(validated_token), validated_token
登录接口在返回 JSON 的同时,也会设置 HttpOnly Cookie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class LoginView (TokenObtainPairView ): def post (self, request, *args, **kwargs ): response = super ().post(request, *args, **kwargs) if response.status_code == 200 : response.set_cookie( "access_token" , response.data["access" ], httponly=True , samesite="Lax" , secure=not DEBUG, max_age=3600 , ) return response
七、服务层设计 视图层应该”薄”——只负责参数校验和响应格式化。真正的业务逻辑封装在 services/ 目录下:
1 2 3 4 5 6 services/ ├── cert_service.py # 合格证业务逻辑(核心) ├── signing.py # Ed25519 数字签名(第四篇详解) ├── pdf_service.py # PDF 合格证生成 ├── qr_service.py # 二维码图片生成 └── wechat_service.py # 微信 OAuth 集成
7.1 cert_service.py —— 业务逻辑中枢 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from django.db import transactiondef create_cargo (product_id, quantity, actor ): """创建货物批次,自动生成批次号和扫码令牌""" product = Product.objects.get(pk=product_id) cargo = Cargo.objects.create( product=product, quantity=quantity, cargo_code=_new_cargo_code(), token=_new_token(), created_by=actor, ) return cargo @transaction.atomic def bind_certificate (cargo_id, cert_data, actor ): """ 将合格证绑定到货物批次。 使用数据库事务确保:要么全部成功,要么全部回滚。 """ cargo = Cargo.objects.select_for_update().get(pk=cargo_id) cert = Certificate.objects.create( cargo=cargo, cert_number=_new_cert_number(), created_by=actor, **cert_data, ) return cert def submit_for_review (cert, actor ): """提交审核:草稿 → 待审核""" if cert.publish_status != "draft" : raise ValueError("只有草稿状态的证书可以提交审核" ) cert.publish_status = "pending_review" cert.save() @transaction.atomic def approve (cert, actor ): """ 审核通过: 1. 状态变更:待审核 → 已发布 2. 生成 Ed25519 数字签名 3. 生成 PDF 合格证文件 4. 记录审计日志 """ if cert.publish_status != "pending_review" : raise ValueError("只有待审核状态的证书可以审批" ) cert.publish_status = "published" cert.approved_by = actor cert.approved_at = timezone.now() cert.signature = signing.sign_certificate(cert) cert.save() generate_pdf_task.delay(cert.pk) def revoke (cert, actor, reason="" ): """吊销证书:已发布 → 已吊销""" if cert.publish_status != "published" : raise ValueError("只有已发布的证书可以吊销" ) cert.publish_status = "revoked" cert.revoke_reason = reason cert.save() def reissue (cert, actor ): """补发证书:基于已吊销的证书创建新草稿,继承核心字段""" if cert.publish_status != "revoked" : raise ValueError("只有已吊销的证书可以补发" ) new_cert = Certificate.objects.create( cargo=cert.cargo, cert_number=_new_cert_number(), product=cert.product, inspection_org=cert.inspection_org, standard_code=cert.standard_code, created_by=actor, reissued_from=cert, publish_status="draft" , ) return new_cert
7.2 其他服务模块概览 pdf_service.py —— 使用 ReportLab 生成 PDF 格式的合格证:
1 2 3 4 5 6 7 8 9 10 from reportlab.lib.pagesizes import A4from reportlab.pdfgen import canvasdef generate_certificate_pdf (cert ): """生成 A4 尺寸的合格证 PDF,包含二维码和数字签名信息""" buffer = BytesIO() c = canvas.Canvas(buffer, pagesize=A4) c.save() return buffer
qr_service.py —— 生成扫码查询用的二维码:
1 2 3 4 5 6 7 8 9 10 11 import qrcodedef generate_qr_image (scan_url ): """ 生成二维码 PNG 图片 scan_url 格式:https://example.com/scan/{token} """ qr = qrcode.make(scan_url, error_correction=qrcode.constants.ERROR_CORRECT_H) buffer = BytesIO() qr.save(buffer, format ="PNG" ) return buffer
wechat_service.py —— 微信扫码登录 OAuth 集成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def get_wechat_auth_url (redirect_uri ): """生成微信授权跳转 URL""" params = { "appid" : settings.WECHAT_APP_ID, "redirect_uri" : redirect_uri, "response_type" : "code" , "scope" : "snsapi_userinfo" , } return f"https://open.weixin.qq.com/connect/oauth2/authorize?{urlencode(params)} " def exchange_code_for_user (code ): """用授权码换取用户信息""" ...
总结 本篇覆盖了后端架构的七个核心模块:
模块
核心思想
环境配置
APP_ENV + django-environ,零硬编码
数据模型
FROZEN_FIELDS 冻结 + 部分唯一约束 + 仅追加审计
视图层
ViewSet + 自定义 action + 认证级联
权限体系
三角色(superadmin / admin / operator)+ 双控审批
中间件审计
threading.local() + ORM 信号 = 无死角审计
JWT 认证
双通道(Header + Cookie)+ Token 轮换
服务层
薄视图 + 厚服务,事务保护关键操作
整个后端的设计哲学可以归纳为:在尽可能低的层级(模型层、数据库层)执行业务规则,让上层代码想犯错都难 。
下一篇预告 :[第三篇] React + Ant Design 前端架构详解——我们将深入前端的路由设计、状态管理、组件拆分,以及如何与后端 API 优雅对接。敬请期待!