资讯动态

Python Web简易订单系统:从MVC分层到Nginx+Supervisor部署实战

发布时间:2026/9/16 15:15:00 来源:尧图企业网站定制
简介该毕业设计项目是一套基于Python Web开发的简易订单系统源码包面向计算机专业毕业生及Python Web初学者帮助理解框架应用与业务系统搭建项目以MVC模式组织后端逻辑、前端模板与数据库脚本一应俱全适合作为毕业设计或课程设计的完整参考。压缩包共95个文件整体仅216KB包含31个Python后端脚本、15个HTML模板、12个JavaScript脚本、7个CSS样式、SQL初始化脚本以及nginx/supervisor部署配置和部署Shell脚本覆盖开发、部署、文档各环节目录结构清晰便于按需查阅。目前已有126人学习下载作为一个轻量级完整项目具备良好的学习热度与参考价值。通过研读源码可以掌握用户登录、订单创建、模板渲染、路由处理、数据库交互等Web开发核心环节同时学习配置文件编写、部署脚本使用和项目文档组织方式从而形成从编码到上线的完整认知。1. 这份压缩包里的订单系统比课程设计多了一整套上线配置拿到基于python web开发的简易订单系统.zip第一反应多数人以为是又一个 Flask/SQLite 教学 Demo但解压后看目录结构会发现另一层信息里面同时出现了src/web、dao、base、bean的分层以及supervisor、nginx、deploy_hjs_cms.sh、hjs_cfg.py这些生产环境才会出现的文件。也就是说这份毕业设计的立意不是「跑通即可」而是试图复刻一个能部署到 Linux 服务器、用 Nginx 反代、被 Supervisor 托管进程的完整 Web 项目骨架。对正在找 Python Web 毕设参考、或者想把课程设计改造成工程化结构的在校生来说这包源码比单独一个app.py有价值得多因为它展示了一条从代码到部署的可走通路径。2. 从 hjs_cfg.py 到 web 层这个系统的 MVC 是手工搭的2.1 目录拆解先看它是怎么组织的解压后的文件清单里没有manage.py也没有典型的settings.py说明这个系统没有依赖 Django而是沿用了轻量框架的「自己管路由、自己管配置」思路。常见的组织方式是把src作为根包内部按职责拆成web控制器层、dao数据访问层、base公共基类、bean实体/模型对象。我一般拿到这种压缩包第一步不是跑代码而是先看hjs_cfg.py和README.md因为这两个文件决定了整个系统怎么启动、连哪个库。. ├── src │ ├── web # 控制器与路由分发 │ ├── dao # 数据库访问层 │ ├── base # 基类、工具类、公共方法 │ └── bean # 实体对象对应数据库表结构 ├── hjs_cfg.py # 全局配置项 ├── secret.py # 敏感配置如数据库密码 ├── hjs_cms_db.sql # 初始化建库脚本 ├── install_hjs_cms.sh ├── deploy_hjs_cms.sh ├── template_deal.py ├── publish_hjs_cms.py ├── conf/nginx # Nginx 站点配置示例 └── conf/supervisor # 进程守护配置示例这种「控制器放 web、SQL 操作收敛到 dao、实体放 bean、公共能力放 base」的划分本质上是 MVC 在 Python 小型项目里的一种轻量实现。项目没有引入重型 ORM而是由dao层直接用 SQL 操作数据库好处是 SQL 可见、执行计划可分析、事务边界清楚坏处是每张表的增删改查都要手写重复代码。所以base层里一般会有一个通用 DAO 基类把连接获取、参数绑定、结果集转对象这些逻辑抽出来子类只写具体 SQL。这是手工 MVC 常用的平衡点不引 ORM但仍消除大部分样板代码。2.2 路由分发web 层怎么把 URL 映射到处理函数看web目录下的代码路由不是 Flask 那种装饰器写法而是更朴素的手工映射表。常见做法是维护一个列表每个元素是一个元组描述「HTTP 方法 URL 模式 处理函数」的对应关系。请求进来后由入口脚本解析 URL 路径遍历映射表做前缀匹配命中了就调用对应的处理函数最后把函数返回值渲染成 HTML 或 JSON。# src/web/router.py # 伪代码风格不同毕设项目的函数名可能不同但结构通常类似 ROUTES [ (GET, /api/order/list, OrderHandler.list_orders), (POST, /api/order/create, OrderHandler.create_order), (GET, /, index_handler), ] def dispatch(environ, start_response): 根据请求路径查找处理器并执行 path environ.get(PATH_INFO, /) method environ.get(REQUEST_METHOD, GET) for route_method, route_path, handler in ROUTES: if method route_method and path route_path: data handler(environ) # 统一把返回值序列化并按约定输出 return render_response(data, start_response) # 未命中路由时返回 404 return render_404(start_response)这段代码的逻辑很简单但有一个参数容易被忽略environ里不仅仅有PATH_INFO和REQUEST_METHOD还包含QUERY_STRING、HTTP_COOKIE、CONTENT_TYPE、wsgi.input等内容。订单列表的查询条件、创建订单时提交的 JSON 体都是通过解析这些字段拿到的。我一般会在web层加一个parse_request(environ)的公共函数把查询字符串和请求体统一解析成字典再传给具体 Handler否则每个处理函数都自己解析代码会重复得很厉害。2.3 配置项拆解hjs_cfg.py 和 secret.py 为什么要分开hjs_cfg.py和secret.py同时存在不是设计冗余而是刻意为之。hjs_cfg.py放的是不敏感的运行参数比如监听端口、调试开关、静态资源路径、上传目录、分页默认大小等secret.py放的是绝对不能进 Git 仓库的东西比如数据库密码、session 加密密钥、第三方平台密钥。这样做的直接好处是把代码仓库分享给导师或同学时可以保留hjs_cfg.py的默认值方便复现同时把secret.py加进.gitignore。这里有一个值得注意的边界问题许多毕设项目把数据库连接串直接写在业务代码文件里导致换一台机器就要全文搜索替换。合格的工程化习惯是写一层get_config()函数统一从配置文件读取# hjs_cfg.py # 数据库连接信息一般通过 import 引用或读取环境变量覆盖 DB_HOST 127.0.0.1 DB_PORT 3306 DB_NAME hjs_cms DB_USER root # DB_PASSWORD 从 secret.py 引入避免出现在默认配置中 DB_CHARSET utf8mb4 DEBUG False PORT 8000 PAGE_SIZE 20配置分离的意义在部署阶段才会真正体现出来。本机调试时用DEBUGTrue和本地密码放到服务器时不需要改任何业务代码只改secret.py或通过环境变量注入生产环境的数据库凭证即可。这个动作看起来只是把密码挪了个文件实际上把「配置」和「逻辑」两个关注点解耦了后续排错时能少很多麻烦。3. 数据库建模与 DAO 层订单状态和库存扣减怎么设计3.1 从 hjs_cms_db.sql 看表设计hjs_cms_db.sql是这个系统的建库脚本几乎所有业务逻辑都围绕里面的表展开。订单系统再简单也至少逃不开这几张表用户表、商品表、订单主表、订单明细表。要判断一个订单系统设计得是否合理我一般先看订单主表和明细表的关系再看是否存在单独的状态字段以及状态字段有没有加索引。以订单主表为例典型的建表语句长这样-- hjs_cms_db.sql 中与订单相关的主表字段名以实际脚本为准 CREATE TABLE t_order ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号业务维度唯一, user_id INT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, paid_at DATETIME DEFAULT NULL COMMENT 支付时间未支付则为NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里值得展开说两个细节。第一order_no必须加唯一索引因为它承担着业务幂等的作用前端用户点击「提交订单」可能触发多次请求后端通过唯一索引拦截重复插入否则会生成多笔相同订单。第二status加普通索引是有意义的因为订单列表页最常见的查询是「查某个用户某种状态下的订单」user_id status的联合索引在数据量上来后效果更明显初期只有几百条数据时感觉不到差别但毕设答辩时老师问到索引设计这是一个合格的回答角度。3.2 DAO 层如何防脏读和超卖订单系统的核心风险不是读写慢而是并发场景下的数据一致性问题。库存扣减是典型的并发写场景如果 DAO 层写得不严谨两个用户同时下单就可能把库存扣成负数。这里给出一个常见且正确的更新写法# src/dao/order_dao.py # 伪代码重点看扣减库存的 WHERE 条件 def decrease_stock(self, goods_id, buy_count): 原子扣减库存防止超卖 sql UPDATE t_goods SET stock stock - %s WHERE id %s AND stock %s params (buy_count, goods_id, buy_count) cursor self.conn.cursor() affected cursor.execute(sql, params) self.conn.commit() return affected这段代码的关键点是一个容易被忽略的参数WHERE id %s AND stock %s。affected为 1 表示扣减成功为 0 表示库存不足或商品不存在此时调用方应该中断下单流程并提示用户库存不足。整个操作不依赖「先查再改」的两步式逻辑而是把条件直接写进 UPDATE 语句由数据库的行锁来保证同一时刻只有一个事务能更新成功这就是抵御超卖的标准解法。事务边界同样要划清楚。创建订单涉及两步写操作向t_order插入主单记录、向t_order_item插入明细、同时扣减库存。这三件事要么全成功要么全失败不能出现订单建好了库存没扣的情况。做法是在web层处理函数的最开始取得连接拿到连接后设置autocommit(False)所有数据库操作完成后commit()任何一个步骤抛异常则rollback()最后在finally里把连接放回连接池。DAO 层只负责单条 SQL 的原子性跨多表的原子性必须靠上层的事务编排这是初学者最容易写反的地方。4. 从开发机到服务器Supervisor Nginx 一键部署脚本4.1 进程守护为什么不能直接python xxx.py就完事把订单系统部署到服务器时如果只是 SSH 进去执行python main.py一旦终端关闭或服务器重启进程就会消失而且进程崩溃后不会自动拉起。这个问题在毕设演示时很致命答辩现场进程挂了场面会很尴尬。项目里出现conf/supervisor目录说明作者已经在用配置化的进程管理方案这是值得保留和展开的部分。Supervisor 的核心作用有两点把 Python Web 进程托管成常驻服务并在进程异常退出时自动重启。典型配置写在conf/supervisor/hjs_cms.conf里; conf/supervisor/hjs_cms.conf [program:hjs_cms] command/usr/bin/python3 /home/hjs/src/web/main.py directory/home/hjs autostarttrue autorestarttrue startsecs5 startretries3 stderr_logfile/var/log/hjs_cms_err.log stdout_logfile/var/log/hjs_cms_out.log environmentPYTHONUNBUFFERED1每个参数都有实际意义我一般会这样理解command是实际启动命令注意一定要写绝对路径因为 Supervisor 启动的子进程不继承你 SSH 会话里的环境变量autostarttrue表示 Supervisor 自身启动时就拉起该程序autorestarttrue是进程崩溃后自动拉起的开关这是生产环境必开项startsecs5指的是程序启动后持续运行 5 秒才认为启动成功避免「启动即崩溃」被误判为正常environmentPYTHONUNBUFFERED1则确保 print 输出不被 Python 缓冲日志能实时写入文件。配置写好后依次执行两条命令即可生效# 把配置文件软链到 supervisor 配置目录 sudo ln -s /home/hjs/conf/supervisor/hjs_cms.conf /etc/supervisor/conf.d/hjs_cms.conf # 读取新配置并启动程序 sudo supervisorctl update sudo supervisorctl status hjs_cmsupdate不写日志直接返回代码不代表服务已经正常启动必须用supervisorctl status看状态是否为RUNNING以及用tail -f /var/log/hjs_cms_out.log看启动日志这一步新手很容易漏。后续改代码想重启服务也用supervisorctl restart hjs_cms而不是去 kill 进程号。4.2 Nginx 反向代理域名与端口怎么衔接conf/nginx目录里的配置解决的是「用户访问 80 端口请求转给 Supervisor 托管在 8000 端口的 Python 进程」这件事。Nginx 的作用不只是转发它还承担静态文件响应、请求大小限制、访问日志记录等功能。如果订单系统的静态资源CSS、JS、图片走的是独立目录可以在 Nginx 层面直接响应这样 Python 进程只处理动态请求能少扛很多并发压力。# conf/nginx/hjs_cms.conf 关键片段 server { listen 80; server_name hjs.example.com; access_log /var/log/nginx/hjs_cms_access.log; location /static/ { alias /home/hjs/src/web/static/; expires 7d; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置里的三个proxy_set_header参数不是随手写的。Host头保证后端业务代码能取到用户访问的域名X-Real-IP和X-Forwarded-For则把用户的真实 IP 传递给后端否则订单系统记录访问日志或做登录审计时看到的所有 IP 都是127.0.0.1这会给排查问题带来误导。改动 Nginx 配置后执行nginx -t检查语法再systemctl reload nginx平滑加载不能直接restart因为 reload 不会中断现有连接。4.3 脚本解读install_hjs_cms.sh 和 publish_hjs_cms.py 在做什么压缩包里有install_hjs_cms.sh和deploy_hjs_cms.sh这类脚本读完能看出作者打包部署的意图。install_hjs_cms.sh一般负责初始化环境比如创建数据库、导入hjs_cms_db.sql、安装 Python 依赖、创建日志目录publish_hjs_cms.py则可能承担发布流程里的模板处理和文件替换。这里我建议毕设项目至少保留两层东西一是环境初始化的 Shell 脚本二是版本发布的执行记录。# install_hjs_cms.sh 常见结构与关键命令 # 1. 创建数据库并导入初始化脚本 mysql -uroot -p ./hjs_cms_db.sql # 2. 安装 Python 依赖requirements.txt 由 pip freeze 生成 pip install -r requirements.txt # 3. 创建日志目录避免 Supervisor 启动时因目录缺失而失败 mkdir -p /var/log/hjs_cms如果脚本里mysql -uroot -p后面没有密码参数说明设计者刻意避免把凭据写死在脚本里这是个好习惯。本地复现时手动执行mysql -uroot -p hjs_cms_db.sql输入密码即可脚本留给自动化环境用环境变量注入密码。5. 上线后怎么验证与排错系统部署完成后最重要的不是功能跑通而是「知道怎么证明它跑通」以及「出问题时怎么定位」。「能用 curl 验证接口」这个习惯建议尽早养成。通过curl直接请求 HTTP 接口检查 HTTP 状态码和返回体就可以在不开浏览器的前提下验证业务入口是否正常。# 验证订单列表接口是否正常返回 curl -i http://127.0.0.1:8000/api/order/list?user_id1返回结果里HTTP/1.1 200 OK表示 Web 进程存活500 Internal Server Error说明 Python 进程抛了异常如果 curl 提示Connection refused则要按链路逐层排查。常见的排查路径是先看supervisorctl status hjs_cms确认进程是否 RUNNING再看/var/log/hjs_cms_err.log里的 Python 堆栈最后检查 Nginx 的 error log 看有没有转发层面的错误。如果是 502 Bad Gateway通常是后端进程挂了或监听端口对不上需要核对nginx.conf里的proxy_pass端口和hjs_cfg.py里的PORT是否一致。这个端口不一致的问题在多个同学的项目里都出现过往往花了很久查代码逻辑最后发现只是配置文件两处没同步。权限相关的问题则是另一类高频坑hjs_cms_err.log里如果出现Permission denied通常是日志目录的所有者是 root而 Supervisor 托管的进程以普通用户运行导致写不进文件。解法是chown -R把日志目录所有者改成运行用户而不是直接用 root 跑服务。最后一个容易忽略的检查点是数据库连接是否因长时间空闲被 MySQL 服务端断开。线上环境里有时会出现白天正常、第二天上班第一次访问报错的情况业务代码里如果没有连接池的自动重连机制进程持有的就是死连接。你的项目如果用的是自写 DAO 层建议在 DAO 基类里加一个轻量重试执行 SQL 前用cursor.execute(SELECT 1)探测连接失败则重新建立连接。这一招能省去很多「莫名其妙就挂了」的排查时间也是这份毕业设计源码里值得自己动手补强的一个小模块。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价