从 Excel 记账到能用的管理系统,中间其实只差一个 Flask 项目。今天要写的这套超市员工供应采购管理系统,就是用 Python 的 Flask 框架搭建的,覆盖了员工档案、供应商管理、采购申请、入库登记、库存查询这些核心流程,适合课程设计、毕业设计,也适合小型超市或便利店直接拿去二次开发。我会把从需求拆解到数据库设计再到功能实现的全过程讲清楚,包含可以直接复制的代码片段和我在实际开发中踩过的坑。
1. 需求建模:超市供应采购的业务流与角色拆解
先别急着写代码。任何管理系统第一步都是搞清楚「谁在用什么流程操作哪些数据」。超市供应采购不同于普通电商,它涉及内部员工、外部供应商、商品库存三个核心维度,而且每个维度之间都有数据联动关系。
1.1 业务对象与关系
超市供应采购系统里最少需要有这几类业务对象:
- 员工:系统操作用户,分为管理员、采购员、仓管员、店长等角色,不同角色能做不同操作。
- 供应商:给超市供货的外部商家,需要记录联系人、电话、供货品类。
- 商品:超市里在售或可采购的商品,包含分类、规格、单位、采购价、售价。
- 采购订单:某个员工向某供应商发出的采购请求,包含商品明细。
- 入库单:供应商送货后,仓库员工验收并入库产生的记录。
- 库存流水:每次入库、出库、退货都产生一条流水,用来追踪库存变化。
这些对象之间不是孤立的。一个采购订单会关联多个商品,一个供应商可以对应多张采购单,一张入库单会同时扣减采购单的待入库数量并增加商品库存。理解了这个关系,才能设计出不会出现数据矛盾的表结构。
1.2 三步主流程
整套系统的主流程可以压缩成三步:
- 采购申请:采购员选择供应商和商品,填写数量和预期单价,生成采购申请单。
- 审核确认:店长或管理员审核采购单,确认无误后状态改为「已审核」,此时才允许供应商送货。
- 入库登记:仓管员根据送货单做出入库登记,系统自动增加对应商品的可售库存,并生成一条入库流水。
你可能会问,为什么采购单不直接入库?因为在实际业务中,审核和入库往往不是同一个人,而且供应商送货数量可能和申请数量有出入。所以把「审核」和「入库」拆成两个独立环节,数据才会真实可信。这也是很多毕设项目最容易做模糊的地方——采购单生成后直接改库存,逻辑上虽然简单,但跟真实业务相去甚远。
1.3 角色与权限边界
权限设计直接决定系统能不能真正落地。在这个超市管理场景里,我建议至少划分四类角色:
- 系统管理员:维护员工账号、供应商资料、商品基础数据,拥有全部权限。
- 采购员:创建采购申请单、查看自己发起的采购单状态。
- 仓管员:执行入库操作、查看库存与流水、登记损耗。
- 店长/经理:审核采购单、查看采购与库存统计报表、导出数据。
权限边界体现在两个层面,一个是功能访问权限,比如仓管员不能点击「审核采购单」按钮;另一个是数据范围权限,比如采购员只能看到自己创建的采购单,店长能看到全部。后者经常被忽略,但对于评分和真实使用来说都很重要。
2. 技术选型与系统架构:为什么是 Flask
这套系统在很多替代方案里选了 Flask,不是因为它最新最潮,而是因为它在「项目结构清晰」「开发效率高」「学习曲线平缓」三者之间平衡得最好。
2.1 Flask 对比 Django 和 FastAPI
很多人在启动项目时会纠结框架选择,这里直接说我的结论。
- 如果目标是快速开发一个内部管理系统,Flask 是够用的:它没有强制目录结构,自由度大,适合中小型项目;扩展生态很完整,SQLAlchemy、Flask-Login、Flask-WTF、Flask-Admin 这些插件能覆盖系统开发九成以上的需要。
- Django 适合大型、功能高度标准化的项目,自带 Admin 后台和 ORM,但它的「全家桶」风格有时候反而是负担,尤其当你只想做一个灵活的管理系统时,重写默认配置的时间可能比写业务功能还多。
- FastAPI 性能确实强,异步模型也很现代,但它更适合 API 服务,而不是返回大量页面模板的传统 Web 管理端。如果你的需求是给超市员工用浏览器点按钮,而不是给外部平台提供高并发接口,FastAPI 的优势在这里并不明显。
所以在这类管理系统里,Flask 是一个务实的选择。它可以让你把精力集中在业务逻辑上,而不是跟框架本身较劲。
2.2 三层架构与项目目录
我在项目里采用的是经典的「视图层-业务层-数据层」三层结构,而不是把所有路由堆在单个 app.py 里。单文件在 Demo 阶段没问题,但一旦加上采购审批、库存流水、报表统计这些功能,单文件很快会膨胀到几千行,后面想改权限都找不到地方。
项目目录结构我习惯这样组织:
supply_management/ ├── app.py # 应用入口,创建 Flask 实例 ├── extensions.py # 存放 db、login_manager 等扩展实例 ├── models/ │ ├── __init__.py │ ├── user.py # 员工用户模型 │ ├── supplier.py # 供应商模型 │ ├── product.py # 商品模型 │ ├── purchase.py # 采购单与采购明细模型 │ └── stock.py # 入库单与库存流水模型 ├── views/ │ ├── __init__.py │ ├── auth.py # 登录、退出、修改密码 │ ├── employee.py # 员工管理 │ ├── supplier.py # 供应商管理 │ ├── product.py # 商品管理 │ ├── purchase.py # 采购申请与审核 │ └── stock.py # 入库与库存查询 ├── templates/ │ ├── base.html # 基础布局模板 │ ├── auth/ │ ├── purchase/ │ └── stock/ ├── static/ │ ├── css/ │ └── js/ ├── config.py # 配置文件 └── requirements.txtextensions.py 单独放 db 和 login_manager 的实例,是为了避免循环导入。因为 models 和 views 都需要用到这些扩展对象,如果直接写在 app.py 里,models 再导入 app.py 就会出现 ImportError。这是一个新手特别容易掉进去的坑,提前把扩展独立出来,后面会很舒服。
2.3 技术栈清单
- Python 3.10+
- Flask 3.x
- Flask-SQLAlchemy 3.x(ORM)
- Flask-Login(会话与登录状态)
- Flask-WTF(表单与 CSRF 防护)
- SQLite(开发环境) / MySQL(生产环境)
- Jinja2 模板(Flask 内置)
- Bootstrap 5(前端布局,CDN 引入即可)
这套组合不需要额外安装 Node.js 之类的前端环节,重点全在后端业务逻辑上,对一个人完成整个项目来说非常友好。
3. 数据库设计:每个表字段背后的业务含义
数据库设计是最能体现系统成熟度的部分。很多人在这个环节草草建四五张表,结果写功能时到处凑数据。我按照前面梳理的业务对象,把表设计成了 8 张,每一张都有明确的业务意义。
3.1 用户与角色表
员工表我命名为users,而不是employee,因为后续做 Flask-Login 登录时,它需要的就是 users 表。表中关键字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer 主键 | 自增 |
| username | String(50) 唯一 | 登录账号 |
| password_hash | String(255) | 只存哈希,不存明文密码 |
| real_name | String(50) | 员工真实姓名 |
| role | String(20) | admin / buyer / keeper / manager |
| phone | String(20) | 联系电话 |
| is_active | Boolean | 是否可登录 |
| created_at | DateTime | 创建时间 |
password_hash 这一列,开发时如果用明文密码,项目在答辩或交付时会被一眼看穿不专业。正确做法是用 Werkzeug 自带的generate_password_hash和check_password_hash。
3.2 供应商与商品表
供应商表suppliers保持轻量,核心用来做采购单的外键关联和通讯录维护:
- id、name、contact_name、phone、address 这些常规字段之外,我额外加了一个
status字段,标记启用/停用。停用的供应商在前台采购选择时直接过滤掉,避免采购员误选已停止合作的供应商。
商品表products是库存管理的核心:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer 主键 | 自增 |
| name | String(100) | 商品名称 |
| category | String(50) | 分类,如饮料、粮油、日化 |
| spec | String(100) | 规格,如 500ml/瓶 |
| unit | String(20) | 计量单位,如箱、瓶、袋 |
| purchase_price | Numeric(10,2) | 最近采购单价 |
| sale_price | Numeric(10,2) | 超市售价 |
| stock | Integer | 当前可用库存 |
| min_stock | Integer | 库存预警下限 |
| supplier_id | Integer 外键 | 主要供货商 |
其中min_stock是容易被忽略但非常实用的字段。库存低于这个值,在系统首页和商品列表里高亮预警,提醒采购员该补货了。这个设计虽然简单,但会让系统看起来「真的有在用」。
3.3 采购单、采购明细与库存流水表
采购主表purchase_orders存一次采购的整体信息:
- order_no:生成的采购单号,如 CG20250607001,不直接用自增 id 当单号,因为单号需要给供应商看,需要有一定的规则感。
- supplier_id:外键关联供应商
- applicant_id:谁创建的采购单
- total_amount:采购单总金额(可以由明细自动汇总再写入)
- status:待审核 / 已审核 / 已入库 / 已取消
- apply_time、audit_time、audit_by
- remark:备注
采购明细表purchase_items存采购单里的商品行项目:
- order_id 外键,关联采购主表
- product_id 外键
- quantity、price、subtotal
库存流水表stock_movements是保证数据可追溯的关键。每次入库、出库、盘亏都写一条记录:
- product_id 外键
- change_type:in / out / adjust
- quantity:变动数量,入库为正、出库为负
- ref_type 和 ref_id:关联来源,比如 purchase_order / stock_in / manual
- operator_id:操作人
- created_at:操作时间
- remark:备注
有流水表之后,库存数不再是一个被直接改写的字段,而是通过流水汇总来核对。如果运营中发现商品库存对不上,就能精确查到是什么时候、谁、通过哪张单子改的。这种设计思路,比单纯 update products set stock = stock + 1 要严谨得多。
3.4 建表初始化与种子数据
模型在 Flask-SQLAlchemy 里直接用 class 定义,以商品模型为例:
from extensions import db class Product(db.Model): __tablename__ = 'products' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) category = db.Column(db.String(50), index=True) spec = db.Column(db.String(100)) unit = db.Column(db.String(20), default='件') purchase_price = db.Column(db.Numeric(10, 2), default=0) sale_price = db.Column(db.Numeric(10, 2), default=0) stock = db.Column(db.Integer, default=0) min_stock = db.Column(db.Integer, default=10) supplier_id = db.Column(db.Integer, db.ForeignKey('suppliers.id')) created_at = db.Column(db.DateTime, default=datetime.utcnow) supplier = db.relationship('Supplier', backref='products')在第一次运行时执行db.create_all()建表,同时用脚本写入一个默认管理员账号和一个演示供应商。开发期这样做很方便,但后面如果要换 MySQL 生产库,建议改成 Alembic 迁移方案,这个放到部署部分再讲。
4. 核心业务功能实现:从登录到入库全链路
这一部分直接给出系统关键功能的实现思路与核心代码,保证你能照着写出来。
4.1 登录与会话管理
登录模块用 Flask-Login 管理用户会话。使用方式非常简单:
from flask_login import login_user, logout_user, login_required, current_user @auth_bp.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user and user.check_password(form.password.data) and user.is_active: login_user(user) return redirect(request.args.get('next') or url_for('purchase.index')) flash('账号或密码错误', 'danger') return render_template('auth/login.html', form=form)注意几个细节:
- 登录成功后跳转的目标要优先取
request.args.get('next'),保证用户在未登录时点击某个功能被拦下来,登录后能回到原来的页面。 login_user(user)之后不要手动写 session,Flask-Login 内部会维护。- 退出时调用
logout_user(),顺带清除 session。
角色权限我用一个自定义装饰器实现。虽然 Flask-Principal 这类扩展可以做更复杂的权限体系,但对当前系统,装饰器足够清晰:
from functools import wraps from flask import abort from flask_login import current_user def role_required(*roles): def decorator(f): @wraps(f) def wrapper(*args, **kwargs): if not current_user.is_authenticated: abort(401) if current_user.role not in roles: abort(403) return f(*args, **kwargs) return wrapper return decorator使用:
@stock_bp.route('/stock/in', methods=['GET', 'POST']) @login_required @role_required('keeper', 'admin', 'manager') def stock_in(): # 入库登记逻辑 pass这里把 keeper、admin、manager 三种角色都允许入库,是因为实际场景里小店可能没有专职仓管员,店长也能代为入库。权限不是越严越好,而是要匹配业务流程。
4.2 采购申请单与审核流程
采购申请是系统中交互最复杂的部分。用户需要选择供应商,然后在一个页面里动态添加多个商品明细,最后提交生成采购单。
我实现的思路是:
- 前端页面上有一个商品明细表格,用户可以选择商品、填写数量、单价,通过 JavaScript 动态增删行。
- 提交时,把多条明细数据以
items[0][product_id]这种列表形式 POST 到后端。 - 后端用
PurchaseOrderItemForm这套嵌套表单来接收数据。
后端接收明细并创建采购单的简化代码如下:
@access_bp.route('/purchase/apply', methods=['GET', 'POST']) @login_required def purchase_apply(): form = PurchaseOrderForm() suppliers = Supplier.query.filter_by(status='active').all() if form.validate_on_submit(): order = PurchaseOrder( order_no=generate_order_no(), supplier_id=form.supplier_id.data, applicant_id=current_user.id, remark=form.remark.data, status='pending' ) db.session.add(order) db.session.flush() # 提前拿到 order.id total = 0 for item in form.items: product = Product.query.get(item.product_id.data) subtotal = item.quantity.data * item.price.data order.items.append(PurchaseItem( product_id=product.id, quantity=item.quantity.data, price=item.price.data, subtotal=subtotal )) total += subtotal order.total_amount = total db.session.commit() flash('采购申请单已提交,等待审核', 'success') return redirect(url_for('access.purchase_list')) return render_template('purchase/apply.html', form=form, suppliers=suppliers)这里有一个值得注意的细节:db.session.flush()后,order.id 会提前生成,但事务并没有提交。这样可以在同一个事务里继续创建关联明细,最后统一 commit。如果中途报错,整个事务一起回滚,不会出现一张主表有记录、明细表空着的情况。
审核操作更简单,本质上就是一个权限校验后的字段更新:
@access_bp.route('/purchase/audit/<int:order_id>', methods=['POST']) @login_required @role_required('manager', 'admin') def audit_order(order_id): order = PurchaseOrder.query.get_or_404(order_id) if order.status != 'pending': flash('该采购单已审核过', 'warning') return redirect(url_for('access.purchase_detail', order_id=order.id)) action = request.form.get('action') if action == 'approve': order.status = 'approved' order.audit_by = current_user.id order.audit_time = datetime.now() flash('采购单已通过审核', 'success') elif action == 'reject': order.status = 'rejected' order.audit_by = current_user.id order.audit_time = datetime.now() flash('采购单已驳回', 'warning') db.session.commit() return redirect(url_for('access.purchase_detail', order_id=order.id))审核按钮在页面上只对 manager 和 admin 角色显示,Jinja2 模板里可以用current_user.role判断。但要记住,前端隐藏按钮只是用户体验优化,真正的权限控制必须靠后端的role_required装饰器,否则别人直接构造 POST 请求就能越权操作。
4.3 入库登记与库存联动
供应商送货到超市后,仓管员在系统里找到「已审核」的采购单,执行入库。入库的核心逻辑是:每条明细都要更新对应商品库存,同时写一条库存流水。
@stock_bp.route('/stock/in/<int:order_id>', methods=['POST']) @login_required @role_required('keeper', 'admin', 'manager') def stock_in(order_id): order = PurchaseOrder.query.get_or_404(order_id) if order.status != 'approved': flash('只有已审核的采购单才能入库', 'warning') return redirect(url_for('access.purchase_detail', order_id=order.id)) for item in order.items: product = item.product prev_stock = product.stock product.stock = prev_stock + item.quantity movement = StockMovement( product_id=product.id, change_type='in', quantity=item.quantity, ref_type='purchase_order', ref_id=order.id, operator_id=current_user.id, remark=f'采购单 {order.order_no} 入库' ) db.session.add(movement) order.status = 'done' db.session.commit() flash('入库完成,库存已更新', 'success') return redirect(url_for('stock.index'))在修改库存之前,可以做一个库存校验:如果item.quantity <= 0或者商品已被禁用,需要中断入库。另外,并发情况下可能出现两个仓管员同时给同一商品入库,这里 SQLite 下简单的做法是给商品行加锁,或者使用数据库事务隔离。对于中小型超市管理系统,理解的顺序是:先保证逻辑正确、有流水可查,再考虑极端并发。
5. 页面交互与前端细节:让系统真正可用的关键点
很多人写管理系统,后端接口通了一看页面就想摔键盘。一套内部管理系统如果页面难用,员工会直接弃用,宁可继续用 Excel。前端不用做得多炫,但交互逻辑要顺。
5.1 模板继承与导航布局
我在基础模板base.html里用 Jinja2 的{% block %}机制划分区域,左侧是导航菜单,右侧是内容区。导航菜单根据角色动态渲染:
{% if current_user.role in ['admin', 'manager'] %} <li><a href="{{ url_for('access.purchase_audit_list') }}">采购审核</a></li> {% endif %}这样做的好处是,不同角色登录后看到的菜单不一样,产品上就叫「菜单级权限」。
5.2 表单处理与消息闪现
所有表单都用 Flask-WTF 定义和校验。以登录表单为例:
from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length class LoginForm(FlaskForm): username = StringField('账号', validators=[DataRequired(), Length(1, 50)]) password = PasswordField('密码', validators=[DataRequired()]) submit = SubmitField('登录')CSRF 防护默认由 Flask-WTF 开启,表单里只要渲染了一次form.hidden_tag(),就会自动带上 CSRF token。这个细节对毕设或正式项目都很重要,因为它能防止跨站请求伪造攻击。
页面顶部显示操作结果,用flash()消息与前端的 Bootstrap alert 结合:
{% with messages = get_flashed_messages(with_categories=true) %} {% for category, message in messages %} <div class="alert alert-{{ category }} alert-dismissible fade show">{{ message }}</div> {% endfor %} {% endwith %}5.3 低库存预警与首页统计
首页设计成一张「管理驾驶舱」式的面板,顶部显示几个关键数字:今日采购单数量、待审核数量、低库存商品数量、供应商总数。低库存商品列表单独列出一张表,按缺口从大到小排序。
库存吃紧的判断条件:
Product.query.filter(Product.stock < Product.min_stock).order_by(Product.stock - Product.min_stock).all()如果想让效果更进一步,可以在首页引入 Chart.js,用 JavaScript 直接读后端传过来的 JSON 数据渲染柱状图或饼图。比如把「各分类采购金额占比」展示出来。实现方式很简单,后端写一个返回 JSON 的接口,前端用fetch获取数据再渲染。这一步不算难,但给评审或老板演示时的观感会明显上升。
6. 本地运行与生产部署:别让系统只活在开发端口里
很多项目跑在127.0.0.1:5000上没问题,一换环境就崩。Deploy 部署这块,我单独说清楚。
6.1 本地跑起来的最快路径
项目 requirements.txt 内容如下:
Flask==3.0.0 Flask-SQLAlchemy==3.1.2 Flask-Login==0.6.3 Flask-WTF==1.2.1 Werkzeug==3.0.1安装依赖后,初始化数据库:
pip install -r requirements.txt python init_db.py python app.py访问 http://127.0.0.1:5000,默认管理员账号 admin,密码 admin123,登录后第一件事就是去员工管理里改密码。
开发服务器默认只监听本机。如果想让超市里其他员工电脑也能访问,启动时改成:
app.run(host='0.0.0.0', port=5000, debug=True)这样同一局域网内的设备就能通过这台电脑的局域网 IP 访问。但debug=True绝不能用于生产环境,它会在报错时暴露完整堆栈,而且允许任意代码执行,非常危险。
6.2 切换生产数据库
SQLite 适合开发和演示,但正式使用建议换成 MySQL 或 PostgreSQL。切换时只需要改配置里的数据库连接串:
# config.py SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://user:password@localhost/supply_db?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False同时把SECRET_KEY从默认值改成环境变量读取:
import os SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-me')生产环境下建议关掉 debug,使用 waitress(Windows)或 gunicorn(Linux)启动。以 waitress 为例:
pip install waitress waitress-serve --host=0.0.0.0 --port=8000 app:appgunicorn 启动方式类似。Nginx 反代放到前面,静态资源由 Nginx 直接服务,动态请求转发给 Flask 端口,这是最标准的小型系统部署架构。
6.3 数据备份与可维护性
内部管理系统最怕数据丢。SQLite 版备份很简单,直接复制数据库文件,但要注意在写入高峰时复制可能出现脏数据。更稳妥的做法是定期停止写入的凌晨,用 sqlite3 的.backup命令,或者换 MySQL 后用 mysqldump 定时导出。
sqlite3 supply.db ".backup 'backups/supply_20250607.db'"7. 踩坑复盘:我这个项目里最值得说的几个问题
最后这部分是我实际开发中遇到过的坑,有的折腾了好几个小时才定位到原因。写出来希望能帮你少走弯路。
7.1 SQLAlchemy 循环导入与上下文问题
我第一次写这个系统时,把db定义在 app.py 里,然后在 models 中导入 app.py,结果一运行就报ImportError或circular import。后来把数据库实例单独抽到extensions.py里,models、views、app.py 三方都只依赖 extensions,问题立即消失。
另一个常见问题是在非请求上下文环境里用db.session。比如有段时间我想在测试脚本里直接调模型方法,没在app.app_context()里包裹就报 "Working outside of application context"。处理办法也很简单:
with app.app_context(): db.create_all() admin = User.query.filter_by(username='admin').first()7.2 模板中直接调用外键关联带来的 N+1 查询
我在商品列表页面展示「供应商名称」时,最初直接写product.supplier.name,看起来很简单,但每一行商品都要多执行一次供应商的查询。当商品几百条时,页面明显变卡。
解决办法是查询时使用joinedload或selectinload,一次性把关联数据查出来:
from sqlalchemy.orm import joinedload products = Product.query.options(joinedload(Product.supplier)).all()修改前后,SQL 数量从 N+1 条变成 1 条,页面的响应速度是肉眼可见的提升。管理系统中类似的 N+1 问题很容易出现,建议从第一个列表页就开始使用joinedload养成习惯。
7.3 角色判断写在模板里的隐患
我一直强调权限控制必须以后端装饰器为准,模板里的if current_user.role == 'admin'只作为用户体验优化。因为模板是可以被前端源码直接看到并绕过逻辑的,如果有人用工具直接 POST 到审核接口,那后端没有校验就完了。所以凡涉及写操作的接口,必须挂上@role_required。
7.4 金额字段用浮点数的后果
超市商品单价如果直接用 Python float 来存,累计采购金额很可能出现 0.30000000000000004 这类诡异小数。解决办法是模型里统一用db.Numeric(10, 2),前端显示时保留两位小数,计算时用 Decimal。一句话总结:钱的问题不要用浮点数,用定点数。
7.5 从「能跑」到「好用」还能怎么扩展
这套系统做完核心流程之后,如果你想继续打磨,我建议从以下方向入手:
- 增加供应商结算模块:把采购入库数据变成每月账单,自动统计应付金额,对接供应商对账。
- 增加商品条码扫描:PDA 或手机扫码入库、盘点,减少手工录入错误。
- 增加预测补货建议:根据近三个月销售数据和当前库存、在途库存,自动生成建议采购量和采购时间。
- 增加操作日志:记录谁在什么时候改了什么关键数据,出现纠纷时有据可查。
这些扩展并不会推翻现有的表结构,因为当初设计时库存流水和采购状态已经为这些场景留好了接口。这也是为什么我说,数据库设计阶段多想一步,后面扩展能省非常多的力气。
整套系统做下来,我的最大感受是:管理系统的难点从来不在某个技术点,而在于梳理清楚业务流程,并把流程忠实地转化成数据流转。Flask 只是把这一切串起来的工具,真正让系统值钱的,是你对「超市怎么采购、怎么入库、怎么对账」这件事的理解。如果你现在正准备动手做类似项目,建议先把业务模型画清楚,再开始写代码,顺序反过来的时候,你多半会推倒重来一遍。