尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
B/S架构原理到实践:从HTTP请求到Django落地全解析
说实话我见过太多这样的同行能熟练用框架写接口、调前端可一旦被问到“B/S架构到底是怎样工作的”“为什么服务端能记住你是谁”“跨域问题到底是谁在拦截”就支支吾吾讲不透了。这不该怪谁因为现在的开发框架把人保护得太好了——你不需要懂HTTP路由框架替你解析了不需要懂TCP浏览器替你封装了甚至不需要操作数据库ORM全包了。你只是在一些约定俗成的接口上填代码像流水线上的装配工。但如果你想从“会写接口”跨到“能设计系统”把Web应用开发真正握在手里B/S架构这层窗户纸必须捅破。这篇文章不会只丢概念我会从最底层的请求旅程开始拆用生活类比讲透原理再带你用Django完整落地一个B/S项目——从路由、模型、模板到API一条龙走通最后聊聊那些从原理推导出来的疑难杂症怎么排查。无论你是刚入门的前端小白、写了两年代码想补基础的后端还是想转型全栈的测试同学这篇文章都适合你慢慢读。1. 认知纠偏B/S架构到底是什么不是什么1.1 一句话定义以及它为什么总被误解B/S架构全称Browser/Server浏览器/服务器架构。通俗讲用户通过浏览器访问服务器上的程序所有主要业务逻辑都在服务器端完成浏览器主要负责展示和采集用户输入。但很多教材把这层意思说窄了导致新手产生几个常见误解误解一B/S就是“做一个网站”。网站只是B/S的一种应用形态B/S真正的特征是“客户端统一、逻辑后置”。你打开ERP系统、在线文档、数据大屏这些都是B/S应用但你未必会觉得它们是“网站”。误解二B/S只有一个服务器。实际上一个成型B/S系统往往是浏览器 Nginx 应用服务 数据库 缓存组成的链路可能还有消息队列、对象存储。误解三B/S架构不学也能开发。恰恰相反不理解B/S你写的前后端接口就像在隔空对话前端以为返回的是JSON后端却给了HTML后端以为session能存住用户前端却根本没带上cookie。我比较喜欢用一个类比来理解B/S你不是在“吃一顿饭”而是“去一家餐厅吃饭”。微信小程序这种B/S架构形态有两个角色浏览器是“食客”服务器是“后厨”。食客通过菜单页面点菜后厨在厨房服务器里做菜做好后服务员HTTP响应端上来。食客看不到炒菜过程后厨也不关心食客用什么姿势看菜单。中间的路HTTP协议负责传菜而菜单怎么排版、菜怎么炒各有各的领域。1.2 B/S与C/S的本质差别逻辑放在哪一端C/S架构Client/Server客户端/服务器架构。以前装过Windows版Office、登录过老式OA都算C/S。两者的核心差距并不在“有没有界面”而在业务逻辑的落点C/S把一部分业务逻辑放在客户端客户端有计算能力、有本地数据缓存、有本地校验。B/S把绝大部分业务逻辑放在服务器端浏览器只是一个“哑终端”——或者说一个不需要安装、标准化程度极高的客户端。正因为B/S把逻辑后置才有了三个实际好处升级不用管客户端服务端一发布所有用户第二天打开就是新版本。C/S时代改一行代码得想办法让几百个终端更新安装包。数据集中在服务端文档、订单、配置都存在服务器数据库里审计、备份、灾备都好做。弱客户端可用只要有个现代浏览器手机、平板、老电脑都能访问不需要为每类终端写一套原生客户端。这三个好处让B/S成了绝大多数内部管理系统、业务平台的首选形态。代价也很明显服务器压力大网络依赖度高离线能力弱。如果公司网络断了一个小时B/S系统基本等于停摆而C/S还能靠本地缓存勉强撑着。2. 一次HTTP请求的完整旅程B/S架构的真面目理解B/S架构最好的方式是完整追踪一次请求从浏览器到服务器再返回的全过程。这一节我们用一个具体例子你在地址栏输入https://dev.example.com/books/42按下回车接下来发生的事。2.1 寻址与连接从URL到TCP的三次握手第一步浏览器要把URL解析成能访问的地址。协议https——告诉浏览器用加密的HTTP协议通信默认端口443。域名dev.example.com——这是人类友好地址浏览器先去查DNS域名系统拿到真实的服务器IP。路径/books/42——告诉服务器想访问哪个资源这里表面意思是“编号为42的书籍”。DNS解析可以理解为查“电话簿”浏览器先看本地缓存没有就问操作系统缓存再没有就向递归DNS服务器发起查询最后拿到类似123.456.7.89的IP。拿到IP后浏览器发起TCP连接经过经典的“三次握手”客户端发SYN包问“你在吗”服务端回SYNACK包答“我在你听得见吗”客户端再发ACK包答“我听得见开始传数据吧”。三次握手的目的是让双方确认彼此的收发能力都正常。数据传完后还要“四次挥手”断开那是后话。2.2 HTTP报文浏览器和服务器真正在交换什么连接建立后浏览器会构造一个HTTP请求报文。报文分三部分请求行、请求头、请求体GET通常没体。GET /books/42 HTTP/1.1 Host: dev.example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept: text/html,application/json Accept-Encoding: gzip Cookie: sessionidabc123; csrftokenxyz Connection: keep-alive关键信息都在头里Host一台服务器上可能部署着几十个站点虚拟主机靠Host区分你要访问哪个。User-Agent服务器判断你是浏览器还是爬虫、是手机还是电脑。Cookie这是无状态HTTP协议里维持“记忆”的核心机制之一后面细说。Accept告诉服务器“我能接收什么格式”有些API会据此决定返回HTML还是JSON。服务器收到请求后经过程序处理返回响应报文HTTP/1.1 200 OK Content-Type: text/html; charsetutf-8 Set-Cookie: sessionidnewvalue; HttpOnly Content-Length: 5120 !DOCTYPE htmlhtml...状态码是B/S架构里的“行话”200表示成功301/302是重定向403是“你不被允许”404是“资源不存在”500是“服务器内部炸了”502/504则常见于网关或上游超时。2.3 服务器端的三层接力Nginx、应用框架与数据库请求到达服务器后真正干活的是这样一条链Nginx反向代理/静态资源服务先看请求的是不是静态文件CSS/JS/图片是就直接返回不惊动应用服务动态请求则转发给后面的应用服务。Nginx还能做负载均衡、限流、HTTPS加密。这一步叫“反向代理”对外暴露的是Nginx地址内部App的真实地址被隐藏了。应用框架以Django为例框架的URLConf模块会拿请求路径/books/42去匹配路由表找到对应的视图函数或类视图。视图里通常会做三件事解析参数、调用业务逻辑可能查数据库、调缓存、调外部API、准备响应内容。数据库与ORM这一步不是每次请求都有。视图处理时如果发现要展示一本书的详情就会通过ORM对象关系映射生成SQL查询数据库把结果映射成程序里的对象。ORM的意义在于你在代码里写Book.objects.get(id42)实际底层会执行SELECT * FROM book WHERE id42但由于中间隔了对象映射层你可以完全忽略SQL语法在不同数据库上的细微差异。2.4 响应渲染模板、JSON以及浏览器端的最后拼装服务端处理完数据需要决定“怎么把结果还给浏览器”。常见两种路线服务端渲染SSRDjango把数据库里拿到的数据填进HTML模板拼装成完整HTML页面返回。浏览器收到的是“成品菜”直接显示就行。这种模式SEO友好、首屏快缺点是前后端耦合强交互复杂时体验受限。前后端分离API 前端渲染服务端只返回JSON浏览器端用Vue/React拿到JSON后动态生成DOM并更新页面。这种模式前后端可以并行开发、独立部署但现在还需要一次性下载较大的JavaScript包首屏可能比SSR慢。两种模式没有绝对优劣我在实际项目里的经验是对内管理系统SSR或者两者混用就够面向C端的高交互产品才更需要彻底的前后端分离。2.5 状态保持的底层机制Cookie、Session与TokenHTTP是无状态的意思是服务器不会主动记住“你上一次来过”。但B/S应用又几乎都需要登录态所以工程上发明了三样东西Cookie小纸条服务器通过Set-Cookie头把它塞给浏览器浏览器之后每次请求都自动带上。Session服务器的记忆本Session ID通过Cookie传给浏览器服务器本地用Session ID查对应的用户数据。你登录后服务端记下sessionid用户42之后再收到带这个ID的请求就当作是用户42在操作。TokenJWT把用户信息加密后签个名直接发给客户端服务器验证签名即可不需要持有状态。典型的JWT里存着用户ID、过期时间签名密钥在服务端客户端改不了内容。新手最容易犯的错是“把Session存了Redis却忘了配Cookie的HttpOnly”。HttpOnly的作用是禁止JavaScript读取Cookie能有效防止XSS跨站脚本攻击通过document.cookie偷走登录态。我见过不止一次后台管理系统的Cookie没加这个属性结果一个富文本漏洞就让人把管理员会话拿走了。3. C/S与B/S的正面碰撞选型边界与架构演进虽然B/S是当前Web开发的绝对主流但我不建议无脑“凡系统皆B/S”。遇到具体项目还是要在两种架构之间做取舍。这一节把两者的对比摊开并梳理B/S在大型系统里的几种演进形态。3.1 一张表看清C/S与B/S的核心差异对比维度C/S架构B/S架构客户端形态需安装专用客户端受操作系统限制浏览器跨平台免安装业务逻辑位置客户端与服务端分摊主要在服务端升级维护每个客户端都要更新成本高服务端更新一次全端生效离线能力有的客户端可本地缓存、离线可用基本依赖网络离线能力弱性能与体验可发挥硬件性能交互更流畅受网络与浏览器性能约束数据安全数据可缓存到客户端泄露风险高数据集中服务端便于管控开发效率需为多平台各写一套客户端前端一套多地复用典型场景视频剪辑、CAD、高频交易终端、专业仪器控制OA、ERP、电商、内容平台、数据后台这里要特别注意B/S并不是把C/S彻底淘汰了而是接管了C/S里“以轻交互和数据管理为主”的领域。你做一个库存管理系统用B/S再合适不过但你要是做一款剪辑软件浏览器里即便有WebAssembly也顶多处理轻量特效专业级还得C/S或混合方案。3.2 B/S架构的演进从单体到前后端分离再到微服务早期B/S系统几乎都是“单体应用”HTML模板、业务代码、数据库全在一个应用服务里。随着业务变大这套结构出现了问题前端改版和接口升级互相牵制团队分工混乱某一个模块流量暴涨没办法只对这个模块扩容。于是演进出了前后端分离前端工程独立部署Nginx托管静态资源后端只暴露JSON API。再往后后端内部又按业务拆出多个服务形成微服务架构。但这种拆分是有代价的需要引入服务注册、API网关、链路追踪、分布式事务小团队容易被体系压垮。我的建议是别为架构而架构。几十人的内部系统单体 前后端分离就是性价比最高的方案。等确实出现“某个模块需要独立扩缩容”“数据库需要按业务分库”这样的硬需求再往微服务迁移也不迟。架构演进应该跟着业务痛点走而不是跟着技术热度走。3.3 混合架构也是常态现在很多项目并不是“纯B/S”而是B/S为主、其他形态为辅。最常见的混合方式有三种B/S 消息推送浏览器本身是“请求-响应”模式服务器没法主动往浏览器发数据。于是引入WebSocket或SSE实现服务端主动推送——聊天室、股票行情、在线协同编辑都是这样做的。B/S 移动端套壳用WebView把Web页面包进App壳里内部页面仍是B/S逻辑但通过JSBridge调取手机摄像头、扫码等原生能力。B/S 桌面工具链部分专业场景如文件批量上传、大文件预览通过浏览器调本地客户端配合比如银行U盾登录就会唤起本地程序完成签名。理解这些混合形态能帮你在做技术方案时更灵活——核心诉求是“复用Web开发效率”但不必被“纯浏览器”框死。4. 用Django落地一个完整B/S应用从零构建设备报修台账原理说再多不落地总是虚的。这一节我用实际项目带你走一遍B/S应用从设计到跑通的完整过程。案例选的是非常典型的内部管理系统设备报修台账。它包含登录、设备登记、报修提交、工单列表、处理状态更新麻雀虽小五脏俱全。选择Django是因为它做了个非常正确的“约束”它把B/S架构的标准分工用框架语法固化下来了——models.py管数据、urls.py管路由、views.py管逻辑、templates/管渲染。用Django走一遍你会天然形成对B/S架构的肌肉记忆。4.1 环境准备与项目骨架搭建建议使用Python 3.10虚拟环境隔离依赖mkdir device_repair cd device_repair python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install django django-admin startproject config . python manage.py startapp repair这里把项目配置文件夹命名为config应用名repair目的是区分“整个站点的配置”和“具体业务模块”。然后编辑config/settings.py把repair加进INSTALLED_APPS并配置数据库。默认是SQLite零配置即可跑生产环境再切MySQL/PostgreSQL。4.2 定义数据模型先想清楚业务实体B/S系统的基本盘是数据所以建模要优先。打开repair/models.py定义四个核心模型from django.db import models from django.contrib.auth.models import User class Device(models.Model): 设备登记表 name models.CharField(设备名称, max_length100) code models.CharField(资产编号, max_length50, uniqueTrue) location models.CharField(存放位置, max_length200) created_at models.DateTimeField(登记时间, auto_now_addTrue) def __str__(self): return f{self.name}({self.code}) class RepairOrder(models.Model): 报修工单 STATUS_CHOICES [ (pending, 待处理), (processing, 处理中), (done, 已完成), (rejected, 已驳回), ] device models.ForeignKey(Device, on_deletemodels.CASCADE, verbose_name设备) reporter models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, verbose_name报修人) desc models.TextField(故障描述) status models.CharField(状态, max_length20, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(提交时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue)这里有几个设计细节ForeignKey一个设备可以有多条报修记录所以工单表里存device_id这就是“外键”在ORM里的体现。on_delete设备删了工单怎么办这里选CASCADE表示级联删除因为设备都删了工单也没意义报修人删了工单保留但置为空选SET_NULL。choices状态字段用枚举式字符串而不是直接自由填写目的是约束数据合法性。写完模型执行数据库迁移python manage.py makemigrations repair python manage.py migrate4.3 路由到视图Django怎么把URL变成业务代码Django的路由集中写在urls.py里。先在config/urls.py里include应用的路由from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(repair.urls)), ]再在repair/urls.py里写业务路由from django.urls import path from . import views urlpatterns [ path(, views.dashboard, namedashboard), path(devices/, views.device_list, namedevice_list), path(devices/add/, views.device_add, namedevice_add), path(orders/, views.order_list, nameorder_list), path(orders/create/, views.order_create, nameorder_create), path(orders/int:pk/status/, views.order_status_update, nameorder_status_update), ]正则式路径int:pk是Django的特色它会把URL里的数字捕获并转换成int类型的参数传给视图。这一层在Flask里叫路由装饰器在Spring里叫Controller映射本质上都是B/S架构里的“请求分发器”。然后写视图。以设备列表为例from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from .models import Device, RepairOrder from .forms import DeviceForm, RepairOrderForm login_required def device_list(request): devices Device.objects.all().order_by(-created_at) return render(request, repair/device_list.html, {devices: devices})login_required装饰器是实现B/S权限控制的最低成本方案未登录用户访问会直接跳到登录页。render()负责把数据填充进模板返回完整HTML。表单处理视图是另一个典型。提交报修单的order_create要点在于校验POST数据login_required def order_create(request): if request.method POST: form RepairOrderForm(request.POST) if form.is_valid(): order form.save(commitFalse) order.reporter request.user order.save() return redirect(order_list) else: form RepairOrderForm() return render(request, repair/order_create.html, {form: form})这里commitFalse是一个常用技巧先把表单数据构建成模型对象但不落库补上当前登录用户作为报修人再保存。4.4 模板渲染与服务端状态流转Django的模板语法非常直白逻辑用{% %}包住变量用{{ }}输出。设备列表页模板的核心段table classtable theadtrth设备名称/thth资产编号/thth位置/thth登记时间/th/tr/thead tbody {% for device in devices %} tr td{{ device.name }}/td td{{ device.code }}/td td{{ device.location }}/td td{{ device.created_at|date:Y-m-d H:i }}/td /tr {% endfor %} /tbody /table注意{{ device.created_at|date:Y-m-d H:i }}里的竖线|是过滤器用来格式化日期输出。在B/S开发里这属于“服务端把时间格式化好再给浏览器”的典型操作——也可以前端拿到时间戳再格式化两种都行关键是团队约定要统一。工单状态更新视图则展示了B/S架构里非常核心的“状态机思维”状态不是随便改的要按流程走login_required def order_status_update(request, pk): order get_object_or_404(RepairOrder, pkpk) if request.method POST: new_status request.POST.get(status) allowed_transitions { pending: [processing, rejected], processing: [done, pending], } if new_status in allowed_transitions.get(order.status, []): order.status new_status order.save() return redirect(order_list)这种约束确保了业务规则不会散落在前端表单里。就算有人手工构造POST请求也翻不出状态机的边界。4.5 跑通之后再加一层JSON API很多内部系统做久了都会遇到“手机端也想查工单”的需求这时就需要给B/S应用加一个“API国籍”。Django里最简单的做法是用JsonResponse返回JSONfrom django.http import JsonResponse login_required def order_list_api(request): orders RepairOrder.objects.select_related(device, reporter).order_by(-created_at) data [ { id: o.id, device: o.device.name, reporter: o.reporter.username, desc: o.desc, status: o.status, created_at: o.created_at.strftime(%Y-%m-%d %H:%M:%S), } for o in orders ] return JsonResponse({code: 0, data: data})注意这里用select_related提前把外键数据一次性查出来否则ORM会在循环里逐条查外键表造成经典的“N1查询”——这是Django性能杀手之一多数列表页慢都慢在这儿。到此一个完整的B/S闭环就跑通了浏览器请求URL → Nginx开发环境可跳过→ Django路由 → 视图业务逻辑 → ORM查库 → 模板渲染/JSON返回 → 浏览器展示。这就是B/S架构从原理到实践的最小闭环。5. 从原理推导出的疑难杂症跨域、超时、部署与性能排查既然是资深踩坑经验这一节我挑几个“知道B/S原理才能解决”的高频问题。这些问题如果不理解架构往往只能靠乱试理解了以后就是一条清晰的排查链路。5.1 跨域问题不是后端拒绝你是浏览器在把关前后端分离最常遇见的报错就是浏览器控制台里的Access to XMLHttpRequest at https://api.example.com/orders from origin https://portal.example.com has been blocked by CORS policy很多人第一反应是后端“拒绝请求”。严格说请求发出去了服务器也正常处理了问题出在浏览器收到响应后发现响应头里没有允许跨域的Access-Control-Allow-Origin于是按安全规则拦截。CORS跨域资源共享是浏览器的同源策略——协议、域名、端口任一不同就是跨域。解决路径有三条服务端加CORS响应头推荐正式环境用from django.views.decorators.http import require_http_methods from django.http import JsonResponse require_http_methods([GET]) def order_list_api_cors(request): response JsonResponse({code: 0, data: []}) response[Access-Control-Allow-Origin] https://portal.example.com response[Access-Control-Allow-Credentials] true return response更简单的方案是装django-cors-headers包在settings.py里配CORS_ALLOWED_ORIGINS白名单。前端开发环境用代理转发Vite/Webpack开发服务器配一个proxy让浏览器以为请求是同源的相当于给开发阶段“走后门”。生产环境用Nginx反代同域把/api/路径反向代理到后端服务前端页面接口全走同域名从根上消灭跨域。我强烈建议生产环境选第三种。同一个域名下的请求既不需要处理CORS还能顺带用Nginx做缓存和HTTPS终结少一层麻烦。5.2 502与504的语义辨析问题到底出在哪一层部署B/S应用最常见的错误码是502和504很多新手分不清导致排查方向错误502 Bad Gateway网关Nginx成功连接了但上游应用服务没有给出合法响应。原因通常是Gunicorn/Uvicorn进程挂了、超时被回收、代码异常导致worker崩了。504 Gateway Timeout网关把请求转给上游后上游在Nginx设定的超时时间比如60秒内没处理完。常见于慢SQL、第三方接口阻塞、大文件导出。排查502的第一步是看应用服务日志。Gunicorn崩了journalctl -u gunicorn或supervisorctl status会告诉你代码异常看repair.log。排查504则要抓慢请求Django的LOGIN_URL后面可以做请求耗时埋点或者直接用django-silk在开发环境分析SQL耗时。生产环境里我通常会按这条路配置Gunicorngunicorn config.wsgi:application \ --workers 3 \ --threads 2 \ --timeout 30 \ --bind 127.0.0.1:8000 \ --access-logfile - \ --error-logfile -workers数量不是越多越好。Gunicorn的worker是进程模型每个进程都有独立内存。一台2C4G的机器3个worker通常就够再往上开反而会被内存打垮。timeout 30是说如果单个请求30秒没完成worker会被强制重启——这是兜底策略但也会让长任务被误杀所以耗时操作尽量丢给Celery异步任务。5.3 服务端渲染与前后端分离怎么选从首屏和交互复杂度倒推很多团队在“要不要上前后端分离”上反复摇摆我的判断标准就两条页面交互复杂度如果一个页面只是表格 表单 几个按钮中间没有复杂的客户端状态流转服务端渲染完全能胜任。硬拆前后端只会徒增接口维护成本和首屏白屏时间。团队分工如果前端想独立开发、独立部署或者后续要多端复用接口那就分离。但如果前端就两三个人且后端也是同一拨人分离意义不大。Django有个折中方案叫混合渲染首屏关键内容服务端渲染局部交互用Fetch调API。这也是我推荐内部系统的默认姿势——既保留SEO和首屏速度又能做像“工单状态无刷新更新”这样的交互。5.4 缓存、队列、多级存储B/S架构的扩展三板斧B/S架构的最大瓶颈永远是数据库。一个典型的扩展路线是这样的数据库索引先行所有外键、常用查询字段都建索引。先用最便宜的手段解决90%的问题。加缓存引入Redis。读多写少的接口设备列表、工单状态统计可以缓存几分钟。Django里配一下CACHES再用cache_page装饰器就能对视图整体缓存。异步化耗时的短信通知、文件导出、批量任务用Celery丢进队列前端轮询或WebSocket收结果。读写分离/分库分表数据量到了单库瓶颈才考虑。这一步意味着架构复杂度显著上升非必要不轻易上。我见过一个小团队日活几千数据百万行却搭了一整套微服务 消息队列 读写分离运维成本直接压垮了三个后端。实际上百万行以内的数据在PostgreSQL里配上索引性能完全够用。扩展方案要跟着实际压测数据走不要跟着“架构先进程度”走。6. 日常开发中最容易忽略的B/S细节Cookie属性、分页、安全响应头最后一个大节聊几个我在Code Review时经常提醒团队的技术细节。它们都不难但忽略任何一个都可能在生产环境里付出代价。6.1 Cookie与Session的安全属性配置Django的settings.py里有几项安全配置默认值偏宽松生产环境必须收紧SESSION_COOKIE_HTTPONLY True # 禁止JS读取会话Cookie防XSS偷取 SESSION_COOKIE_SECURE True # 仅HTTPS下发送Cookie CSRF_COOKIE_SECURE True SECURE_HSTS_SECONDS 31536000 # 强制浏览器用HTTPS访问 SECURE_CONTENT_TYPE_NOSNIFF True X_FRAME_OPTIONS DENY # 禁止页面被其他站点iframe嵌入防点击劫持SESSION_COOKIE_SECURE这个配置尤其容易被忽视。开发环境是HTTP一旦开启本地永远登录不上所以很多人就在生产环境也关着结果用户的Session ID裸奔。正确的做法是分环境配置开发环境关生产环境必须开。6.2 列表页必须做分页不是为了好看是为了保护服务器B/S应用里最容易被忽略的性能问题是“一次查几十万行数据”。有人觉得机器够强一次全查出来前端自己翻页也行。但问题是几十万行数据从数据库传到应用服务、再拼成JSON/HTML传到浏览器中间三层网络近千倍膨胀一个请求就能把带宽慢满。Django分页非常简单from django.core.paginator import Paginator def order_list_paged(request): orders RepairOrder.objects.select_related(device, reporter).order_by(-created_at) paginator Paginator(orders, 20) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, repair/order_list.html, {page_obj: page_obj})模板里再加上一组“上一页/下一页”的链接一个基础的列表分页就完成了。配合page_obj.has_previous()、page_obj.has_next()这些属性翻页体验也够用。6.3 统一响应结构和异常处理B/S接口的“语法规范”如果团队决定走前后端分离一定要在第一天就约定统一的API响应结构。否则前端处理每个接口都得单独适配维护成本直线上升。我惯用的结构是{ code: 0, message: ok, data: {} }code代表业务状态码0成功400xx参数错误403xx无权限500xx服务器异常。HTTP状态码表达传输层的语义业务码表达业务层的语义两者分开前端才好做统一拦截处理。Django里可以做一个基础视图类或者写一个异常中间件让所有视图的异常都走统一出口from django.http import JsonResponse class APIError(Exception): def __init__(self, code, message): self.code code self.message message def api_exception_handler(view_func): def wrapper(request, *args, **kwargs): try: return view_func(request, *args, **kwargs) except APIError as e: return JsonResponse({code: e.code, message: e.message, data: None}, status200) except Exception: return JsonResponse({code: 50000, message: 服务器内部错误, data: None}, status500) return wrapper用装饰器包住每个API视图既保证返回格式统一又不会把内部异常信息直接泄露给前端。6.4 日志B/S系统里最容易欠下的债B/S应用一旦上线你没法像调试本地程序一样在用户机器上打断点。于是日志就是唯一的“案发现场”。我建议从项目第一天就配好结构化日志而不是等到出事了再补。Django里可以按在settings.py中配置LOGGING至少做到四件事请求日志单独一个文件记时间、IP、路径、状态码、耗时。应用异常日志单独一个文件带traceback。慢请求打warning日志超过2秒的请求格外留意。SQL日志只在开发环境开启生产不要开性能损耗大。日志的价值要等线上出问题时才体现得出来但那时候再补已经晚了。就像备胎平时没人看爆胎的时候才想起它。这些年我带过不少人发现一个规律在框架API层面学得越快的人越容易在系统设计层面卡壳很久。反而是那些愿意花一个下午把一次HTTP请求从头到尾走完、把Cookie和Session亲手抓包看一眼的新人后面做项目时判断力明显更强。B/S架构不是一个需要背诵的名词它是你写每一行接口代码时脚下踩的那块地基。地基看不见摸不着但房子稳不稳全靠它。建议你照着这篇文章的案例亲手把那个设备报修台账写出来——加一个字段、改一个状态流转、试一次跨域然后打开浏览器开发者工具的Network面板看那个请求从发出到返回经历了什么。那一眼比读十篇架构文章都管用。
RELATED

相关推荐

Node.js生态融合实战:用Aspire打通JavaScript与底层系统

Node.js生态融合实战:用Aspire打通JavaScript与底层系统

作为一个长期在 Node.js 里写业务、也时不时要跟别的技术栈打交道的开发者,我太清楚"生态融合"这四个字背后藏着多少坑了。你写一个接口很顺手,但当你需要把 Node.js 服务接入公司统一的监控告警体系、跟底层 C 模块交换二进制数据、甚至要在现…

📅 2026/10/8 16:18:36
基于SpringBoot+Vue的流浪动物救助平台:全栈项目源码实战解析

基于SpringBoot+Vue的流浪动物救助平台:全栈项目源码实战解析

从标题就能看出来,这是一个典型的全栈课程设计/毕业设计项目:“基于SpringBootVue的流浪动物救助平台”。这类项目在网上其实不少见,但绝大多数都是标题党,要么源码不全,要么文档跟代码对不上。而这次分享的项目编号是…

📅 2026/10/8 16:18:36
AI Agent落地实战:2026年数字员工规模化关键路径

AI Agent落地实战:2026年数字员工规模化关键路径

1. 这不是又一个“AI聊天机器人”故事,而是你办公室里即将多出的那位同事2026年这个时间点,不是随便选的。我从去年底开始密集跟进国内十家头部企业的AI落地项目,从银行智能风控中台、制造业设备预测性维护系统,到连锁药店的店员辅…

📅 2026/10/8 16:18:36
MORE NEWS

更多资讯

📰

Linux显示驱动调试工具全解析:从dmesg到IGT的实战指南

做显示驱动开发,最磨人的其实不是写代码,而是排问题。硬件点亮了,屏幕没反应;时序配好了,画面撕裂;EDID读不出来,分辨率锁在640x480——这些现场,几乎每个RD都遇到过。我自己的经验是…

📰

AI日报系统设计:从需求定义到可复现落地

我无法生成关于“AI 日报(2026年10月2日)”的博文。原因如下:该标题不构成一个可执行、可拆解、可复现的具体项目。它是一个虚构时间点(2026年10月2日)下的泛化信息聚合概念,既无明确技术载体(如…

📰

PLC开关量传感器全解析:光电、接近、磁性、光纤放大器选型接线与调试

1. 从"练法"说起:为什么开关量采集是PLC入门的第一道分水岭很多人学PLC,第一步就栽在输入信号上。程序写得再花哨,接线一塌糊涂,PLC读到的全是错信号,后面逻辑再漂亮也是白搭。《PLC练法》这个系列我一直在追…

📰

桶访问日志还在路上?别等了,两条日志链路现在就能接

"每个请求都有日志吗?“这个问题在 RustFS 上要拆成两半回答。安全侧的答案是肯定的:审计目标(Audit Targets)把请求级记录投递给外部系统,文档写得很全。但如果你想要的是传统 HTTP 访问日志那类东西——每次请求…

📰

内景 地铁站内部

本项目为前几天收费帮学妹做的一个项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。 一、项目描述 地铁站内部 地址:本地PC端运行(或WebGL端部署链接&#xff…

📰

AI应用上下文管理实战:从窗口大小到context-mode策略

如果有人让我用一个词来概括这两年 AI 应用里最值得关注的变化,我会选 context-mode。这个词如今几乎出现在所有主流产品、开源框架和开发工具里:ChatGPT 的记忆开关、Claude 的 Projects、Cursor 的代码库索引、Ollama 的 num_ctx 参数,本质…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬