
简介JavaWeb作为后端开发的核心技术体系涵盖了Servlet、JSP、JDBC等基础组件是理解Web应用运行原理的关键路径。通过一个完整的业务系统可以将这些分散的技术点串联成一条清晰的开发链路。在工程实践中Servlet负责请求分发与逻辑控制JSP承担页面渲染JDBC则完成数据库交互三者的协作方式决定了项目的可维护性与扩展性。当业务复杂度上升时事务控制、连接池管理、分层架构设计等底层能力变得尤为重要这也是开发者从入门走向进阶的必经环节。典型的应用场景包括课程设计、毕业设计以及企业级Web系统原型开发。本文以外卖点餐系统为例围绕用户端、管理端、订单流转、购物车等核心模块从数据库表设计、前后端实现到部署调试系统拆解了基于JSPServletMySQL的技术方案并针对常见问题给出了可落地的解决方案帮助开发者快速构建一个功能完整的JavaWeb项目。 大学里做过 JavaWeb 课设的同学应该对外卖点餐系统这个题目都不陌生。它几乎是 Web 开发入门阶段的经典项目用户端、管理端、订单流转、购物车该有的业务场景全都有但又不至于像电商平台那样庞大到无从下手。我最早用 JSPServlet 写这个系统花了三周过程中踩了不少坑也趟出了一些经验正好借着这篇内容完整复盘一下。这篇文章主要面向正在做 JavaWeb 课程设计、毕业设计或者想通过一个完整案例梳理 JavaWeb 知识体系的同学。我会从整体设计、数据库表结构、前后端实现、部署运行、问题排查这几个维度把整个系统从 0 到 1 拆开讲清楚文中涉及的代码思路和实操步骤都是基于我自己写这套系统时采用的常见方案你可以直接拿来参考也可以根据自己项目的需求调整。1. 项目整体设计与技术选型思路1.1 外卖点餐系统的核心需求拆解在写第一行代码之前我的习惯是先把业务需求画成流程图理清角色和核心流程。外卖点餐系统最常见的角色有两类用户和管理员有些项目会细分出商家端、配送员端但课程设计阶段绝大多数情况是用户管理员的双角色结构。用户端的核心流程是一条线走下来的注册登录 → 浏览菜品 → 加入购物车 → 提交订单 → 查看订单状态。管理员端的核心流程则是维护这条线下游的数据登录后台 → 管理菜品分类和菜品信息 → 查看用户订单 → 更新订单状态。从需求反推功能模块系统其实只需要拆成几个模块用户模块、菜品模块、购物车模块、订单模块。每个模块内部再细分成“前端操作界面”和“后台Servlet处理逻辑”两条线。1.2 为什么选 JSPServletMySQL 这套组合这一套组合放在今天的生产环境里确实不主流了但作为学习项目它的定位非常准确。JSP 负责页面展示Servlet 负责接收请求和处理业务逻辑JavaBean 封装数据JDBC 负责数据库访问整个流程完全覆盖了 JavaWeb 的核心知识点。我见过不少同学一上来就纠结要不要用 SpringBoot、MyBatis-Plus 这些框架。如果这是毕业设计用框架没问题但如果是课程的阶段作业我的建议是先不要用框架。原因很简单框架封装的粒度越大你离底层原理越远而 Servlet 的生命周期、HttpServletRequest 和 HttpServletResponse 的交互、JDBC 的事务控制这些东西恰恰是课程考察的核心。亲手用原生技术写一遍后面学框架反而更快理解框架替你做了什么。整个系统的技术选型我列了一个参考配置组件推荐选型说明JDKJDK 1.8Tomcat 8.5 稳定兼容企业遗留项目最常见Web容器Tomcat 8.5JSP/Servlet 标准容器部署调试简单数据库MySQL 5.7 或 8.05.7 兼容性好8.0 注意驱动和时区参数前端JSP CSS/JS结合 Bootstrap 快速搭建界面开发工具IDEA 或 EclipseIDEA 的 Ultimate 版自带 Tomcat 集成1.3 项目分层结构的理解JavaWeb 项目虽然不像现在框架那样强制分层但我强烈建议你遵守一个简单的分层约定Servlet 层只做请求接收和参数解析业务逻辑写在 Service 类里数据库操作放在 DAO 类里实体类用 JavaBean 承载数据流转。这样的分层有几个实际好处。第一后续如果要加功能不需要在 Servlet 里堆代码每个类的职责清晰。第二代码量到了一定规模之后出了问题第一个排查的定位范围会小很多。第三答辩的时候老师问你“这块逻辑在哪个文件里”你分分钟指给他印象分会好很多。我的实际分包方式是com.example.entity 实体类User、Dish、CartItem、Order com.example.dao 数据库访问层JdbcTemplate风格手写 com.example.service 业务逻辑层处理事务边界 com.example.servlet 控制层WebServlet注解或web.xml配置 com.example.util 工具类DBUtil、MD5加密等唯一需要注意的一点是业务逻辑不能塞在 DAO 里。我有一个同学就是把下单选菜的判断逻辑全写在 DAO 类里后来订单状态一复杂改一处要动三个方法特别被动。DAO 只做最简单的增删改查涉及多表联动的逻辑放在 Service 层这是 JavaWeb 项目里性价比最高的一个习惯。2. 数据库设计与核心表结构2.1 从订单数据反推表结构数据库设计是外卖点餐系统里最值得花时间的地方。我的经验是不要直接对着功能清单一个一个建表而是从核心业务输出倒推。这套系统里最重要的业务输出是“用户下单”那就先思考一个问题一张订单需要保存哪些数据订单本身需要记录订单编号、下单用户、下单时间、订单总金额、订单状态。但订单里具体包含了哪些菜有几份单价多少这是另一类数据必须单独建一张订单明细表。两条数据放一张表会出现大量冗余而且不利于后续扩展“一个订单包含多个菜品”的关系。有了这个思路再往上游推用户需要注册登录所以要有用户表菜品需要分类展示所以要有菜品表和分类表用户下单前选了哪些菜需要一个临时的载体这就是购物车。购物车在传统 JavaWeb 项目里有两种实现方式用 Session 临时保存或者用数据库表持久化。课程设计阶段用 Session 就能满足要求但如果想让项目看起来更完整也可以建一张购物车表。2.2 核心表的字段设计与关联关系我在这套系统里最终设计了五张核心表每张表的字段都是基于业务需求精简过的不建议照抄但大致思路可以给你参考。用户表 user字段名类型说明idint 主键自增用户IDusernamevarchar(50) 唯一登录名passwordvarchar(100)密码建议MD5存储phonevarchar(20)用户手机号addressvarchar(255)默认收货地址create_timedatetime注册时间菜品分类表 categoryid、name、sort_order。分类表单独建表的好处是以后你加麻辣烫类、奶茶类、快餐类不需要改动代码逻辑往表里插入数据就行。菜品表 dishid、category_id、name、pricedecimal(10,2)、image、description、sales销量、status。这里最关键的是 category_id 和 dish 表形成外键关联查询时一次 join 就能把分类名带出来。订单表 ordersid、order_no订单编号、user_id、total_amount、status枚举值如待支付/待接单/配送中/已完成/已取消、create_time、remark。order_no 我习惯用时间戳加上随机数生成避免并发下单时重复。订单明细表 order_detailid、order_id、dish_id、dish_name、price、quantity、subtotal。这里冗余存一份 dish_name 和 price 是刻意为之因为菜品价格和名称后续可能修改但订单的历史快照必须保持创建那一刻的数据这是电商系统设计的常见做法。以这段建表 SQL 为例订单明细表的核心结构大致是这样CREATE TABLE order_detail ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, dish_name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, subtotal DECIMAL(10,2) NOT NULL, CONSTRAINT fk_order_detail_order FOREIGN KEY (order_id) REFERENCES orders(id) );2.3 建表时的几点实操建议字段类型的选择上有一个常见的大坑价格字段一定不要用 float 或 double而要使用 decimal。float 会有精度丢失比如 0.10.2 可能在数据库里得到 0.30000000000000004在金额场景里这是不能接受的。decimal(10,2) 表示最长 10 位数字保留两位小数足够应对外卖订单的金额范围。varchar 长度也要提前想好。用户地址建议给 255备注字段给 255菜品描述给 255 或更大。我就遇到过一次菜品的 description 在 JSP 页面死活显示不全排查了半天才发现是数据库字段长度不够被截断了。另外建议给时间字段统一加上默认值比如 create_time 直接设置 DEFAULT CURRENT_TIMESTAMP这样插入数据的时候少写一个参数也不容易漏。索引方面课程设计阶段不需要太多优化但 user_id 和 order_id 可以加上普通索引因为后续查询“某个用户的订单列表”“某个订单的明细”是最高频的操作索引能明显加速。加了索引之后MySQL 的 WHERE 条件查询走的是索引扫描而不是全表扫描数据量小的时候体感不明显数据量大的时候差距会拉得很大。3. 前后端核心功能实现与实操要点3.1 用户端菜品展示、购物车与下单流程用户端的菜品展示功能在实现上比较简单核心就是一个查询列表的 Servlet从数据库查出菜品信息后放到 request 域里然后转发到 JSP 页面通过 JSTL 标签循环渲染。这里有一个比较容易忽略的点菜品列表页通常需要显示分类名而菜品表里只有 category_id所以查询语句要么用 JOIN 关联分类表要么在实体类里额外加一个 categoryName 字段来接收查询结果。我用的是前者SQL 写起来更直观。购物车的实现有两种方案课程设计阶段我更推荐用 Session。用一个 Map 来存购物车key 是菜品 IDvalue 是某个自定义的 CartItem 实体里面包含菜品实体、数量、小计金额。这样用户即使在未登录状态下也能把菜加入购物车下单前再校验登录状态体验更自然。核心逻辑是这样一个结构HttpSession session request.getSession(); MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); } CartItem item cart.get(dishId); if (item null) { item new CartItem(dish, 1); } else { item.setQuantity(item.getQuantity() 1); } cart.put(dishId, item); session.setAttribute(cart, cart);这串代码的思想本质是一个缓存读写Session 本身就是一个服务端内存存储相当于给你免费提供了一个临时的“购物车数据库”。当然它也有缺陷服务端重启后用户购物车数据就丢了但在课程设计场景里这种取舍是可以接受的并且能在答辩时讲清楚它的局限性和替代方案比如存数据库或 Cookie反而是加分项。下单流程是整个系统里业务逻辑最密集的地方。用户点击提交订单后Servlet 要做的事情不止是往数据库插入一条订单记录还包括计算订单金额、扣减菜品库存或更新销量、生成订单明细、清空购物车。这些操作如果分步执行且没有事务保护就会出现“订单已生成但库存没减”的数据不一致问题。事务的写法在 JDBC 里并不复杂。在 Service 层获取 Connection关闭自动提交执行完所有 DAO 操作后统一 commit任何一步抛异常就 rollback最后在 finally 里恢复自动提交并关闭连接。你可以用一个统一的事务管理思路Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); orderDao.insertOrder(conn, order); orderDetailDao.batchInsert(conn, detailList); dishDao.increaseSales(conn, dishId, quantity); conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException(下单失败, e); }需要特别提到一个细节操作数据库的 DAO 方法如果涉及事务Connection 应该在 Service 层创建并通过参数传下去而不是在 DAO 内部各自获取连接。不然每个 DAO 操作各用各的连接事务根本管控不到。这是很多初学者容易犯的错误我一开始也是把 getConnection 写在 UserDao 里后来做订单事务时才发现根本没法保证原子性。3.2 管理端菜品管理与订单状态流转管理端的功能本质上是用户端的反向操作用户端把数据写入数据库管理端则对这些数据进行维护和审核。菜品管理模块是典型的增删改查关键点在于图片上传课程设计阶段我的做法是让用户上传图片后把文件保存到项目的 upload 目录下数据库里只记录文件名或相对路径。展示时通过 img 标签的 src 指向 upload 目录下的文件。有个细节很多人容易踩坑IDEA 中 Tomcat 运行项目时项目目录和最终部署目录可能不是同一个。如果你把图片保存在了源码目录下的 upload运行时访问不到是正常的因为 Tomcat 实际部署的是 target/out 目录下的副本。所以我建议在本地调试时把上传路径配置成 Tomcat 安装目录下的 webapps/ROOT/upload或者干脆写一个配置类读取绝对路径避免路径错乱的问题。订单状态流转也是管理端的重点。我的设计方案是在订单表里用 int 类型存状态值0 待接单1 配送中2 已完成3 已取消。管理端提供一个按钮来修改状态这个按钮会通过 Ajax 请求或表单请求提交到后台后台根据当前状态判断下一个允许的状态阻止非法跳转。这样设计的本质是状态机思想每个状态对应一组允许的迁移动作防止用户绕过流程把订单从“待支付”直接跳到“已完成”。3.3 请求路径与 Servlet 映射设计Servlet 的 URL 映射看着简单但映射得不好会让整个项目乱成一团。我的经验是用模块前缀来组织路径/user/* 开头的路径表示用户端接口/admin/* 开头的路径表示管理端接口/cart/* 表示购物车操作。举个例子用户端下单接口是 /user/order/submit管理端订单列表是 /admin/order/list。这样即使项目里的 Servlet 数量涨到二十几个URL 结构依然清晰而且过滤器可以根据路径前缀精确判断拦截范围。用 WebServlet 注解配置映射时需要特别注意 URL Pattern 的写法。比如 WebServlet(/order/submit) 默认匹配的是精确路径不会自动匹配 /order/submit/xxx。如果希望一个 Servlet 匹配多种请求方式通常的做法是写成 WebServlet(/order/*)然后在 doPost 里获取 request.getPathInfo() 判断具体子路径。这里面的细微差别在开发时不容易感觉出来但遇到一次 404 你就会记住它。4. 完整部署流程与 IDEA 运行踩坑实录4.1 环境准备我用的开发环境是 JDK 1.8 IDEA 2022.2 Tomcat 8.5.87 MySQL 5.7这些版本互相兼容性很好建议你的版本尽量接近否则可能出现驱动不兼容、编译报错等连锁问题。如果用的是 MySQL 8.0驱动类名是 com.mysql.cj.jdbc.Driver并且连接 URL 需要额外加上 serverTimezoneAsia/Shanghai否则启动时大概率会报时区相关的错误。JDBC 连接配置我习惯放在一个 db.properties 文件里而不是直接硬编码在 Java 类中。这样换数据库、改密码的时候只需要改配置文件不用重新编译排查问题也更方便。配置内容大致是jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/food_delivery?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password1234564.2 导入项目与数据库初始化步骤拿到一个新的 JavaWeb 项目压缩包第一步先不要急着导入 IDEA先检查一下解压后的目录结构。标准 Maven 项目应该有 pom.xml 和 src 目录非 Maven 项目则会有 src 目录和 .project/.classpath 等文件。如果是第一次接触这类项目我建议按这个顺序操作第一步把压缩包里的 SQL 文件找出来用 Navicat 或命令行执行将数据库表和测试数据导入本地 MySQL。导入成功后先查看 user 表里是否已有测试账号如果数据库里没有数据前端页面菜单列表只会是空的。第二步在 IDEA 中打开项目。如果是 Maven 项目等依赖下载完成后直接配置 Tomcat如果是普通 Web 项目需要先配置 Project Structure把源码目录标记为 Sources把 web 目录标记为 Web Resources。这里需要注意不同 IDEA 版本的界面位置不完全一样但核心操作逻辑是一致的。第三步修改数据库连接配置。重点检查数据库名、用户名、密码是否与本地一致这一步不改对的话项目跑起来后十有八九会报数据库连接失败。第四步配置 Tomcat。IDEA 中点击 Run → Edit Configurations → Add New → Tomcat Server → Local部署选项卡里把项目的 war exploded 包添加进去Application context 建议改为 /这样访问路径不会多一层项目名省心不少。4.3 本地调试与真机测试的区别很多人会在本地用 localhost:8080 访问项目觉得一切正常。但真正上线部署或在局域网里用手机访问时就会遇到地址问题。localhost 只代表本机访问其他设备是无法通过这个地址访问你的服务的。如果要让局域网内的其他设备测试需要把访问地址改成你电脑的局域网 IP比如 192.168.x.x:8080同时检查防火墙是否放行了 8080 端口不然手机浏览器会一直连接超时。如果项目要部署到云服务器过程其实是类似的把项目打成 WAR 包放到服务器上的 Tomcat webapps 目录下启动 Tomcat 就会自动解压并部署。数据库也需要迁移到服务器的 MySQL 实例中这一步建议用 mysqldump 导出 SQL 文件再导入比手工建表靠谱得多。4.4 常见部署场景对照场景操作方式注意点IDEA 本地运行Tomcat Local 配置确认 application context 不冲突局域网访问使用局域网 IP 访问关闭防火墙或放行端口云服务器部署WAR 包放入 webapps 目录数据库 URL 改为服务器地址5. 常见问题与排查技巧实录5.1 404 页面出现时的排查方向404 的原因通常不在数据库层面而是请求路径没匹配上。我遇到的最多情况是页面表单里写的是 /user/loginServlet 里映射的却是 /user/Login字母大小写不一致Tomcat 里路径是区分大小写的。排查时先看 IDEA 控制台有没有输出“Not found mapping for POST /xxx”之类的日志没有的话再看浏览器 F12 的 Network 面板里请求的实际 URL 是否和预期一致。还有一种情况是项目访问路径带了多层前缀比如 application context 配置成了 /food 而代码里写的是 /user/login那么实际访问路径应该是 /food/user/login。如果你在 JSP 里用相对路径写请求地址很容易出现路径少一层或多一层的错误。解决思路是统一用绝对路径在 JSP 页面最前面通过 request.getContextPath() 获取上下文路径再拼接请求路径这样无论部署到哪个目录都不会出现路径问题。5.2 500 错误与数据库连接异常500 错误里最常见的组合是 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 和 Communications link failure。前者说明 MySQL 驱动 JAR 没有被打进项目的 lib 目录后者说明数据库地址或端口不对。还有一种是 Access denied for user说明用户名或密码错误。如果项目是 Maven 工程大概率是 pom.xml 里没有加 MySQL Connector/J 依赖或者加了但版本和本地 MySQL 不兼容。如果是普通 Web 工程检查 WEB-INF/lib 目录下是否有 mysql-connector-java-xxx.jar。这里有一个 IDEA 的坑点把 JAR 包直接拖到某个目录并不等于让它生效需要右键 JAR 包选择 Add as Library才能把 JAR 添加到项目的 ClassPath 中。5.3 中文乱码问题的完整解法中文乱码基本可以分成三种来源页面显示乱码、请求参数乱码、数据库存储乱码三种乱码的解决方式完全不同一次处理不好就会来回折腾。页面显示乱码的根源在于 JSP pageEncoding 和浏览器解析编码不一致。统一的做法是在 JSP 页面头部声明 pageEncodingUTF-8服务器给浏览器的响应头里也声明 content type 为 UTF-8这样页面和浏览器都是 UTF-8就不会出现乱码。请求参数乱码分为 GET 和 POST 两种情况。POST 请求乱码在 Servlet 中通过 request.setCharacterEncoding(UTF-8) 解决但这个设置必须在读取任何请求参数之前调用。为了方便通常会写一个全局过滤器统一处理编码。GET 请求乱码比较特殊因为参数在 URL 上Tomcat 8.5 默认 URI 编码是 UTF-8一般不会出问题但如果是老版本 Tomcat就需要在 server.xml 的 Connector 节点上加 URIEncodingUTF-8。数据库存储乱码需要同时检查三个环节连接 URL 是否带 characterEncodingutf8 参数数据库表是否使用 utf8mb4 字符集JDBC 驱动版本是否过旧。三处统一之后基本能把乱码问题彻底解决。5.4 Tomcat 端口占用与启动失败启动 Tomcat 提示 Port 8080 was already in use 是最经典的问题。解决方式很简单找到占用端口的进程并结束它就行。Windows 下用 netstat -ano | findstr 8080 查看占用进程 PID然后 taskkill /F /PID 对应 PID 结束进程。如果你电脑上曾经启动过多个 Tomcat 实例而没有正常关闭这种占用很常见。另外说一下 JSP 编译问题Tomcat 启动后第一次访问 JSP 页面会因为 JSP 编译成 Servlet 而存在短暂的延迟这是正常现象不要以为项目卡死了。如果 JSP 改完不生效很多时候是因为浏览器缓存了旧的页面按 CtrlF5 强刷一下就能看到最新效果。6. 项目扩展与进阶方向6.1 引入 Maven 管理依赖如果原始项目还是传统的方式在 WEB-INF/lib 下手工管理 JAR 包我建议你尝试迁移到 Maven。Maven 最大的好处是把第三方依赖的版本统一管理和自动下载项目的构建过程也标准化了。IDEA 对 Maven 项目支持非常完善迁移成本其实不高新建一个 pom.xml 文件把需要的依赖按坐标写进去删掉原来手动粘贴 JAR 包的目录IDEA 会自动帮你识别和导入。迁移完成之后项目的可维护性会明显提升。比如要升级 JDBC 驱动版本只需要改 pom.xml 里的版本号不需要重新粘贴文件。这也为以后学习 SpringBoot 等基于 Maven 的框架打好了基础。6.2 从 JSP 加 Servlet 到 SpringBoot 的平滑过渡如果你已经吃透了这套原生 JavaWeb 代码下一步可以尝试把项目迁移到 SpringBoot。两种技术在数据访问层面虽然完全不同但业务逻辑本质是一样的Service 层的下单事务控制、DAO 层的数据读取、控制层接收请求参数并返回结果。SpringBoot 只是把这些层之间的胶水代码自动化了你的思路完全可以复用。迁移过程中最值得花心思理解的是 SpringMVC 的请求映射方式JSP 时代用 WebServlet 注解初始化一个 ServletSpringMVC 则是用 Controller 定义一个类方法并标注 RequestMapping。理解了这层对应关系JavaWeb 到 SpringBoot 的过渡其实只是换个姿势写接口并不是重新学一遍编程。6.3 前端升级与扫码点餐的实际玩法很多学校的课程设计要求里都出现了“扫码点餐”这个关键词这其实不需要自己实现硬件层面的扫码核心思路就一句话每一桌生成一个唯一的二维码二维码的链接指向你系统的点餐页面并且带着 tableId 参数。用户扫出二维码之后后台通过参数知道是哪一桌在点餐下单时把 tableId 一并存进订单表。这个功能如果用 JSP 实现思路也是完全可行的前端用 JS 生成二维码图片可以引入 qrcode.js 库链接指向 /user/order/menu?tableIdxxx用户点餐提交订单时带上 tableId 参数即可。不需要把技术栈推到多高的高度核心是把业务流程跑通。这也恰好说明了 JSP 时代的老项目想往“扫码点餐、外卖配送”的方向扩展并没有想象中那么复杂。6.4 支付与实时配送整合空间如果想让项目在答辩时更有亮点可以考虑对接第三方沙箱支付。支付宝和微信都有沙箱环境在测试环境里模拟整个支付流程不用真的申请商户资质。订单模块加入支付状态后下单流程变成“提交订单 → 跳转支付 → 支付回调 → 通知商家”多了一个状态链路整个项目的业务完整度会提高一个层次。实时配送功能在课程设计阶段很难真正实现实时定位但可以做一个简化的“配送模拟”机制管理员点击开始配送后系统记录一个配送开始时间前端页面倒计时显示预计送达时间配送完成后更新订单状态。这类功能从技术角度看并不复杂但对业务理解深度和项目完成度都很有帮助。最后再分享一点个人经验回头看我写外卖点餐系统那段时间最大的收获其实不是把代码跑通了而是通过一个完整项目把 JavaWeb 的知识缝成了一个整体。单独学 Servlet、JSP、JDBC 的时候每个知识点都是孤立的等到项目把所有模块串起来才真正理解了请求是怎样从浏览器一层一层传到数据库又是怎样返回页面渲染的。这个过程没法跳过也没有捷径。如果你现在正在写这个项目我的建议是不要只满足于把功能调通可以试着给自己增加一点点额外要求——比如给管理端加一个销量统计页面或者给订单状态加一层异常处理。这些看似很小的升级会逼着你去理解更多细节而这些细节往往才是面试官真正会问的东西。希望这篇文章能帮你少走一些弯路祝你的系统一次跑通。本文还有配套的精品资源点击获取