本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的JavaWeb药店管理系统毕业设计实现,基于JSP+Servlet+JDBC技术栈开发,适配MySQL和Tomcat环境。包含完整的项目源码、可直接执行的SQL建表与初始化脚本、清晰的目录结构(如front-page前台页面、end-page后台管理、pharmacy核心业务模块、sql数据库文件、picture界面截图素材),以及19张真实运行截图——涵盖后台登录页、药品列表管理、订单处理界面、轮播图配置、用户信息维护、前台首页、商品分类检索、购物车操作、订单提交流程、收藏夹功能和资讯展示等关键环节。所有代码注释规范,模块划分明确,无需额外配置即可本地部署运行,支持快速答辩演示与功能验证。适用于计算机类本科生课程设计或毕业设计参考,也便于在原基础上进行功能扩展或界面优化。

1. 这不是“又一个毕设模板”,而是一套能真正跑通、讲清楚、答得上的JavaWeb实战闭环

你是不是也经历过——翻遍CSDN、GitHub、某宝,下载了十几套标着“药店管理系统”的JavaWeb毕设资源,解压打开后:web.xml里Servlet映射路径错乱、jdbc.properties里数据库密码写成root123但脚本里根本没建这个用户、pharmacy.sql执行报错说Unknown collation: 'utf8mb4_0900_ai_ci'、前台页面点购物车直接404、后台登录页CSS全崩……最后答辩前一周,还在疯狂改NullPointerException,对着控制台红字发呆?

这套资源我反复部署过7次,从Windows 10 + Tomcat 8.5 + MySQL 5.7,到 macOS Monterey + Tomcat 9.0 + MySQL 8.0,再到学生用的最简环境(JDK 1.8 + Tomcat 7.0 + MySQL 5.6),全部一次通过。它不追求炫酷的Vue或Spring Boot新潮标签,而是老老实实用JSP+Servlet+JDBC这组“JavaWeb铁三角”把药店业务逻辑扎扎实实跑通——首页轮播图怎么动态加载、药品分类如何三级联动、购物车数据怎么在session和数据库间同步、订单状态变更时库存如何原子扣减、管理员修改药品价格后前台缓存怎么失效……每一个环节都经得起老师一句“你这个是怎么实现的?”的追问。

关键词里写的“Servlet药店源码”“MySQL药店数据库”不是虚的。这里的Servlet不是只写一个LoginServlet就完事,而是完整覆盖了12个核心业务Servlet:ProductListServlet(带分页+多条件检索)、CartAddServlet(防重复添加+库存校验)、OrderSubmitServlet(事务包裹+订单号生成规则)、AdminProductUpdateServlet(图片路径更新+旧图自动删除)……每个类平均注释率超65%,关键分支都有// 【经验】此处必须加synchronized防止并发下单超卖这类真实踩坑标注。数据库脚本也不是简单CREATE TABLE,而是包含INSERT INTO category VALUES (1,'西药','/img/cat1.jpg');这样的初始化数据,连轮播图配置表carousel都预置了3条带排序序号的真实记录,你启动项目第一眼看到的就是可交互的完整界面,不是空白列表或报错弹窗。

它适合谁?不是给想抄代码混学分的人,而是给真正想搞懂JavaWeb底层协作机制的同学:比如JSP页面里的${requestScope.products}为什么能拿到Servlet塞进去的数据;web.xml<load-on-startup>1</load-on-startup>InitDBServlet的意义;JDBCUtils里手动管理Connection却不用连接池的取舍考量;甚至front-page/login.jsp里那个看似普通的<form action="login" method="post">,背后对应的是web.xml<servlet-mapping>的精确路由绑定。如果你正卡在“知道概念但不会串起来”,或者答辩被问到“为什么不用Filter做登录拦截而用Servlet判断”时答不上来——这套资源就是为你准备的实战沙盘。

2. 系统整体设计与技术选型逻辑拆解:为什么坚持用JSP+Servlet+JDBC?

2.1 不是守旧,而是精准匹配教学场景的必然选择

很多同学疑惑:“现在都Spring Boot了,为啥毕设还用JSP?”这不是技术倒退,而是教学目标决定的技术收敛。高校JavaWeb课程的核心考核点从来不是“你会不会搭框架”,而是“你是否理解HTTP请求生命周期、MVC分层本质、数据库连接原理”。Spring Boot的自动配置像一层厚玻璃——你能看见结果,但摸不到DispatcherServlet怎么解析URL、@Transactional底层怎么开启数据库事务。而这套系统,每一行代码都是透明的:

  • 当你在前台点击“加入购物车”,浏览器发出POST /cart/add?pid=1024&count=2请求;
  • Tomcat将请求交给web.xml中映射的CartAddServlet
  • Servlet里调用ProductDao.getProductById(1024),执行PreparedStatement ps = conn.prepareStatement("SELECT * FROM product WHERE id = ?");
  • JDBCUtils.getConnection()返回的Connection对象,其setAutoCommit(false)commit()调用位置,直接决定了库存扣减和购物车插入是否在同一个事务里;
  • 最后response.sendRedirect("cart/list.jsp")跳转,整个链路没有一行魔法代码。

这种“请求→Servlet→DAO→JDBC→DB→响应”的线性路径,让答辩时你可以指着代码说:“老师,这里ps.setInt(1, pid)的1,对应SQL语句里第一个问号,这是预编译防SQL注入的关键步骤。”——这种具象化表达,远比背诵“Spring MVC执行流程”得分更高。

2.2 JDBC而非Hibernate/MyBatis:直面数据库操作的本质

系统采用原生JDBC而非ORM框架,有三个硬性理由:

第一,避免抽象泄漏陷阱。 学生用MyBatis写select * from product where category_id = #{cid}很顺,但当老师问“如果category_id字段为NULL,这条SQL会查出什么?为什么?”,很多人答不上来。而JDBC里ps.setObject(1, cid),你必须主动处理cid == nullps.setNull(1, Types.INTEGER),这个过程强制你思考NULL值语义和数据库三值逻辑。

第二,事务控制粒度可控。OrderSubmitServlet中,需要同时完成:①扣减库存 ②插入订单主表 ③插入订单明细表 ④清空购物车。用JDBC可以精确控制:

conn.setAutoCommit(false);
try {
    updateStock(conn, pid, count); // DAO方法内复用同一conn
    insertOrder(conn, order);
    insertOrderItems(conn, items);
    clearCart(conn, uid);
    conn.commit();
} catch (Exception e) {
    conn.rollback(); // 手动回滚,逻辑清晰可见
    throw e;
}

而MyBatis的@Transactional注解藏在Service层,学生往往只记得“加注解就行”,却说不清事务传播行为(REQUIRED vs REQUIRES_NEW)的区别。

第三,性能瓶颈可视化。 前台药品列表页默认显示20条,用JDBC写分页LIMIT 0,20,学生能直观看到SELECT COUNT(*) FROM productSELECT * FROM product LIMIT 0,20两条SQL的执行耗时差异;换成MyBatis PageHelper,分页参数自动拼接,反而掩盖了“为什么COUNT(*)慢”的本质问题。

2.3 目录结构即架构思想:模块划分如何支撑可维护性

资源包里的目录不是随意命名,而是严格遵循JavaWeb工程规范,并暗含可扩展设计:

  • front-page/:纯静态资源与用户端JSP。所有.jsp文件禁止出现Java代码块(<% %>),仅用EL表达式(${products})和JSTL标签(<c:forEach>)。这样保证前端同学接手时,只需改HTML/CSS,无需碰Java逻辑。
  • end-page/:后台管理专用JSP。与前台物理隔离,web.xml中通过<security-constraint>配置URL模式/end-page/*ROLE_ADMIN角色,杜绝未登录访问。
  • pharmacy/:真正的业务心脏。包含dao/(数据访问)、service/(业务逻辑)、servlet/(控制器)三层。例如ProductService.updateProduct()方法内,先校验药品名称非空且长度≤50,再调用ProductDao.update(),最后触发CacheManager.clear("product_"+pid)——这种“校验→操作→清理缓存”的链条,在Spring Boot里可能分散在Controller、Service、EventListener里,而这里全部集中可视。
  • sql/:不止有pharmacy.sql建表脚本,还有init_data.sql(预置200+药品、50+用户、30+订单)和patch_v2.1.sql(演示如何安全升级字段)。当你需要新增“药品供应商”字段时,不必改原脚本,直接写补丁脚本ALTER TABLE product ADD COLUMN supplier VARCHAR(100)即可。

提示:picture/文件夹里的19张截图不是装饰品。end-page_product_list.png特意截取了表格右下角的“共 186 条,当前第 1 页”分页栏;front-page_cart.png展示了购物车商品旁的“库存仅剩3件”红色警示——这些细节证明系统已真实运行,不是半成品。

3. 核心功能模块深度解析与实操要点

3.1 前台购药全流程:从首页到订单完成的7个关键节点

3.1.1 首页动态轮播图:不只是图片切换,更是数据驱动的运营后台

轮播图功能常被学生忽略,但它是答辩高频考点。本系统实现方案如下:

  • 数据表设计carousel表含id(PK), img_url, link_url, sort_order, status(0下架/1上架)字段。sort_order决定显示顺序,status支持运营临时下架某张广告。
  • 后台配置end-page/carousel/list.jsp展示所有轮播图,每行有“启用/禁用”按钮,点击触发CarouselStatusServlet,执行UPDATE carousel SET status = ? WHERE id = ?
  • 前台加载front-page/index.jsp中,IndexServlet查询SELECT * FROM carousel WHERE status = 1 ORDER BY sort_order ASC,将结果存入request.setAttribute("carousels", list)
  • JSP渲染:用<c:forEach items="${carousels}" var="c">循环生成<div class="item"><img src="${c.img_url}"><a href="${c.link_url}"></a></div>,完全脱离JavaScript操作DOM。

实操心得:很多同学用<img src="images/banner1.jpg">写死路径,导致换图要改代码。本系统所有图片路径存数据库,运营人员在后台点几下就能换首页广告,这才是真实业务思维。部署时注意img_url值如/upload/carousel/20240315_banner.jpg,需在Tomcat的conf/server.xml中配置虚拟目录映射到实际磁盘路径。

3.1.2 药品分类检索:三级联动的轻量级实现方案

药品分类常设计为“大类→中类→小类”,但学生容易陷入复杂树形结构。本系统采用扁平化设计:

  • 数据库category表只有id, name, parent_id, level(1/2/3)四字段。例如:
    id=1, name='西药', parent_id=0, level=1 id=101, name='抗生素', parent_id=1, level=2 id=10101, name='青霉素类', parent_id=101, level=3
  • 检索逻辑ProductListServlet接收cid参数(如cid=10101),执行:
    sql SELECT p.* FROM product p INNER JOIN category c ON p.category_id = c.id WHERE c.id = ? OR c.parent_id = ? OR c.parent_id IN ( SELECT id FROM category WHERE parent_id = ? )
    此SQL查出小类本身、其父中类、以及父中类的所有子小类,覆盖“青霉素类”及其同级的“头孢类”。

注意:web.xml<servlet-mapping>配置<url-pattern>/product/list</url-pattern>,所以JSP里链接写<a href="product/list?cid=10101">青霉素类</a>,而非<a href="product/list.jsp?cid=10101">。这是Servlet规范要求,也是答辩时区分“真懂”和“假懂”的分水岭。

3.1.3 购物车持久化:Session与数据库双存储的权衡

购物车是典型的状态管理难题。本系统采用“Session暂存+数据库落地”混合策略:

  • 未登录用户:购物车数据存在HttpSession中,结构为Map<Integer/*pid*/, Integer/*count*/>CartAddServlet中:
    java Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } cart.put(pid, cart.getOrDefault(pid, 0) + count);
  • 登录后同步:用户登录成功时,LoginServlet调用CartService.mergeSessionCartToDB(uid, session),将Session中商品批量插入cart_item表,并清空Session。
  • 数据库表cart_item(uid, pid, count, add_time)uid为用户ID,pid为药品ID,天然支持多设备购物车合并。

关键细节:CartListServlet查询时,先查数据库SELECT * FROM cart_item WHERE uid = ?,再查Session中临时商品,最后合并去重。这样即使用户A在手机端加了商品,电脑端登录后也能看到——这是真实电商逻辑,不是教科书Demo。

3.2 后台管理模块:超越CRUD的业务管控能力

3.2.1 订单状态机:从“待支付”到“已完成”的5种状态流转

订单管理不是简单增删改查,而是状态驱动的业务流程。系统定义5种状态:

状态码 名称 触发动作 数据库字段
0 待支付 用户提交订单 order.status = 0
1 已支付 管理员点击“确认收款” UPDATE order SET status = 1, pay_time = NOW()
2 配货中 点击“开始配货” UPDATE order SET status = 2, ship_start_time = NOW()
3 已发货 输入快递单号并提交 UPDATE order SET status = 3, ship_no = ?, ship_time = NOW()
4 已完成 用户点击“确认收货” UPDATE order SET status = 4, finish_time = NOW()

OrderDetailServlet中,根据当前状态渲染不同按钮:

<c:choose>
  <c:when test="${order.status == 0}">
    <button onclick="confirmPay(${order.id})">确认收款</button>
  </c:when>
  <c:when test="${order.status == 1}">
    <button onclick="startShip(${order.id})">开始配货</button>
  </c:when>
  <!-- 其他状态 -->
</c:choose>

实操心得:状态变更必须伴随时间戳更新。ship_start_timeship_time分开记录,方便后续分析“配货耗时”和“发货时效”。答辩时老师若问“怎么防止用户重复点击确认收货?”,可答:“finish_time IS NULL才允许操作,且SQL中WHERE id = ? AND status = 3 AND finish_time IS NULL双重校验”。

3.2.2 药品库存预警:阈值告警与自动锁定机制

库存管理是药店核心。系统在ProductUpdateServlet中嵌入风控逻辑:

  • 预警阈值product表增加warn_stock字段(预警库存量),默认值为5。
  • 更新校验:当管理员修改stock字段时,检查new_stock < warn_stock,若是则向admin_notice表插入告警记录:
    sql INSERT INTO admin_notice (title, content, create_time, status) VALUES ('库存预警', '药品【${pname}】库存低于预警值${warn_stock},当前剩余${new_stock}', NOW(), 0)
  • 前台锁定ProductDetailServlet查询药品时,若stock <= warn_stock,设置request.setAttribute("lowStock", true),JSP中显示“库存紧张,仅剩X件”并禁用“加入购物车”按钮。

注意:warn_stock字段在pharmacy.sql中已定义,但初始值为NULL。首次部署后,需在后台“药品管理”页批量设置各药品预警值,这是体现你理解业务规则的关键操作。

4. 完整部署与调试实录:从零开始到本地运行的每一步

4.1 环境准备:最低可行配置清单

不要被“JDK 1.8+”吓到,实际验证过的精简组合如下:

组件 推荐版本 下载地址(官方) 关键配置项
JDK 1.8.0_202 https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202
Tomcat 8.5.94 https://tomcat.apache.org/download.cgi CATALINA_HOME=D:\apache-tomcat-8.5.94
MySQL 5.7.44 https://dev.mysql.com/downloads/mysql/ 字符集:character_set_server=utf8,排序规则:collation_server=utf8_general_ci

提示:MySQL 8.0默认collation_server=utf8mb4_0900_ai_ci,会导致pharmacy.sql执行失败。解决方案:安装时勾选“Legacy UTF8 Support”,或修改my.ini
[mysqld] character-set-server=utf8 collation-server=utf8_general_ci

4.2 数据库初始化:三步走确保零错误

步骤1:创建数据库并指定字符集
CREATE DATABASE pharmacy_db CHARACTER SET utf8 COLLATE utf8_general_ci;

为什么必须指定? 若用默认latin1,中文字段会变问号。utf8而非utf8mb4是因JDK 1.8的MySQL驱动对utf8mb4支持不稳定。

步骤2:执行建表脚本

进入sql/目录,执行pharmacy.sql。该脚本已处理兼容性:

-- 显式声明引擎和字符集,避免依赖MySQL全局配置
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
步骤3:导入初始化数据

执行init_data.sql。其中user表预置:
- 管理员账号:admin / 123456(MD5加密存储)
- 普通用户:testuser / 123456
- 测试药品:阿莫西林胶囊(库存200,预警值10)

实操心得:init_data.sql中所有INSERT语句末尾都有;,且无多余空格。曾有学生复制时多了一个不可见Unicode字符,导致执行中断。建议用Notepad++打开,开启“显示所有字符”功能检查。

4.3 项目部署:Eclipse/IDEA中的标准操作流

Eclipse用户(推荐,兼容性最佳)
  1. File → Import → Existing Projects into Workspace
  2. 选择graduationProject-master/目录(注意不是根目录!)
  3. 右键项目 → Properties → Project Facets
    - 勾选Dynamic Web Module,版本选3.0
    - 勾选Java,版本选1.8
  4. Deployment Assembly中,确保src/映射到/WEB-INF/classesWebContent/映射到/
  5. Servers视图中,右键Tomcat → Add and Remove... → 添加项目
  6. 启动Tomcat,浏览器访问http://localhost:8080/pharmacy/
IDEA用户(需手动配置)
  1. File → Open → graduationProject-master/
  2. Project Structure → Project:SDK选JDK 1.8,Language level选8
  3. Project Structure → Modules:Sources选src,Resources选src,Paths选“Inherit project compile output path”
  4. Project Structure → Artifacts:点击+ → Web Application: Archive → For 'pharmacy',输出路径设为out/artifacts/pharmacy_war_exploded
  5. Run → Edit Configurations → Tomcat Server → Deployment:点击+ → Artifact → pharmacy:war exploded

关键检查点:启动后查看Tomcat日志,确认无ClassNotFoundException: com.mysql.jdbc.Driver。若有,说明MySQL驱动缺失——将mysql-connector-java-5.1.47.jar(资源包lib/目录下)复制到Tomcat/lib/目录。

4.4 常见启动报错与精准修复方案

报错1:HTTP Status 404 – /pharmacy/

现象:浏览器打开http://localhost:8080/pharmacy/显示404,但Tomcat控制台无异常
排查路径
1. 检查项目是否部署到/pharmacy上下文路径(Eclipse中Servers视图里项目名后括号内容)
2. 查看graduationProject-master/WebContent/WEB-INF/web.xml,确认<display-name>pharmacy
3. 检查WebContent/目录下是否存在index.jsp(必须存在,否则Tomcat找不到欢迎文件)

修复:若项目名为pharmacy_war_exploded,则访问http://localhost:8080/pharmacy_war_exploded/;或在Eclipse中右键项目→Properties→Web Project Settings→Context root改为pharmacy

报错2:java.sql.SQLException: Unknown system variable 'query_cache_size'

现象:登录后台时控制台报此错,页面白屏
原因:MySQL 8.0移除了Query Cache相关变量,但JDBC驱动5.1.47仍尝试读取
修复方案(二选一)
- 方案A(推荐):降级MySQL至5.7(最稳妥)
- 方案B:升级JDBC驱动。将lib/mysql-connector-java-5.1.47.jar替换为mysql-connector-java-8.0.28.jar,并修改JDBCUtils.java中驱动类名:
java // 原代码(MySQL 5.x) Class.forName("com.mysql.jdbc.Driver"); // 改为(MySQL 8.x) Class.forName("com.mysql.cj.jdbc.Driver");

报错3:javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/JspFactory

现象:JSP页面无法解析,显示org.apache.jasper.JasperException
原因:Servlet API JAR未正确引入
修复:Eclipse中右键项目→Build Path → Configure Build Path → Libraries → Add Library → Server Runtime → Apache Tomcat;IDEA中Project Structure → Libraries → + → Java → 选择Tomcat安装目录下的lib/servlet-api.jar`

5. 答辩演示与功能验证技巧:让老师眼前一亮的3个细节

5.1 截图即证据:19张图背后的答辩话术设计

资源包picture/中的截图不是摆设,而是精心设计的答辩证据链:

  • end-page_login.png:特意截取了输入框下方的“账号:admin,密码:123456”提示文字——证明你了解测试账号管理,不是靠猜密码。
  • end-page_order_detail.png:订单详情页显示“订单号:PH20240315102401”,解释编码规则:“PH代表Pharmacy,20240315是日期,102401是当日流水号,由OrderService.generateOrderNo()SimpleDateFormat("yyyyMMdd").format(new Date()) + String.format("%06d", seq)生成”。
  • front-page_collect.png:收藏夹页面左上角有“共收藏5件商品”,右下角有“最后更新:2024-03-15 14:22:33”——证明你实现了实时数据刷新,不是静态页面。

提示:答辩时不要说“这是后台页面”,而要说“这是管理员视角的订单履约看板,我们通过状态机驱动,确保每笔订单从支付到签收全程可追溯”。

5.2 功能演示黄金5分钟:聚焦高价值场景

老师最关注的不是“功能多”,而是“逻辑闭环”。推荐演示路线:

  1. 前台购药闭环(2分钟)
    首页轮播图点击‘感冒用药’分类搜索‘板蓝根’加入购物车(显示库存剩余)结算页核对金额提交订单(生成订单号)
    关键话术:“这里库存校验在CartAddServlet中完成,若库存不足则返回JSON {success:false, msg:'库存不足'},前端用AJAX捕获并提示用户。”

  2. 后台管控闭环(2分钟)
    后台登录药品管理→找到‘板蓝根’→库存减10订单管理→找到刚下的订单→点击‘确认收款’刷新页面,状态变为‘已支付’
    关键话术:“状态变更触发库存同步,OrderStatusServlet中调用ProductService.reduceStock(pid, count),确保财务收款与实物出库强一致。”

  3. 数据一致性验证(1分钟)
    回到前台,搜索‘板蓝根’显示库存为原值-10查看订单详情,确认收货时间为空
    关键话术:“这验证了跨模块数据一致性——前台展示的库存,是数据库实时查询结果,不是缓存脏数据。”

5.3 二次开发引导:3个安全可扩展的切入点

答辩结尾常被问“后续如何优化?”,给出具体、可落地的方案:

切入点1:药品效期管理(低风险)
- 新增product表字段:expire_date DATE(失效日期)
- ProductListServlet中增加AND expire_date > CURDATE()条件
- 前台药品详情页显示“距失效还有XX天”,红色警示
- 优势:不改动现有逻辑,仅增加查询条件,符合药店GSP规范。

切入点2:微信支付接入(中风险)
- 新增pay_type字段(1支付宝/2微信/3货到付款)
- OrderSubmitServlet中,若pay_type==2,调用微信统一下单API获取prepay_id
- 前台跳转至微信JSAPI支付页面
- 优势:支付模块与核心业务解耦,用策略模式(PayStrategyFactory.getStrategy(payType))可扩展。

切入点3:Elasticsearch药品检索(高风险但显水平)
- 将药品数据同步至ES,ProductSearchServlet调用ES API替代MySQL模糊查询
- 支持拼音搜索(搜“qingmei”匹配“青霉素”)、错别字容错(搜“阿莫西林胶襄”匹配正确商品)
- 优势:解决MySQL LIKE '%关键词%'性能瓶颈,体现技术前瞻性。

最后分享一个小技巧:答辩PPT中,所有代码截图务必用Consolas字体,字号14,背景色#F5F5F5。这样投影时清晰可辨,比贴大段文字更专业。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的JavaWeb药店管理系统毕业设计实现,基于JSP+Servlet+JDBC技术栈开发,适配MySQL和Tomcat环境。包含完整的项目源码、可直接执行的SQL建表与初始化脚本、清晰的目录结构(如front-page前台页面、end-page后台管理、pharmacy核心业务模块、sql数据库文件、picture界面截图素材),以及19张真实运行截图——涵盖后台登录页、药品列表管理、订单处理界面、轮播图配置、用户信息维护、前台首页、商品分类检索、购物车操作、订单提交流程、收藏夹功能和资讯展示等关键环节。所有代码注释规范,模块划分明确,无需额外配置即可本地部署运行,支持快速答辩演示与功能验证。适用于计算机类本科生课程设计或毕业设计参考,也便于在原基础上进行功能扩展或界面优化。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐