简介这套问卷调查系统以SSM框架为后端支撑前端采用layui组件和JSP动态页面数据库选用MySQL面向具备Java基础的学生或希望快速搭建在线问卷系统的开发者。项目将管理员与普通用户分离管理员端涵盖管理员信息维护、问卷制作与发布、调查结果统计和报表展示用户端支持问卷填写与提交权限边界清晰功能覆盖完整可直接作为课程设计或毕业设计蓝本。压缩包共包含847个文件总大小约42.05MB其中既有31个Java源文件、36个JSP页面用于业务逻辑与页面展示也有138个JavaScript脚本和44个CSS样式文件负责前端交互与美化38个XML配置和35个Jar包支撑框架运行另有300张GIF演示图直观展示各模块操作过程目录层级清楚并附带SQL脚本与txt说明便于快速还原环境。资源至今已有1226人学习或下载整体研读后可掌握SSM环境搭建、MyBatis映射文件编写、layui表格和表单组件调用以及前后端数据交互与问卷统计报表的实现细节稍作调整即可适配其他调研场景。通过逐步复现源码还能理解数据库设计、控制层转发与业务逻辑封装的关键写法适合做二次开发和毕业设计参考。1. 问卷调查系统一个能跑通完整前后端链路的 Java 课设这套基于 SSM 的问卷调查系统是 Java 后端项目里很典型的“管理端 用户端”双角色工程。管理员可以维护账号、制作问卷、发布问卷、查看统计报表用户拿到链接就能在线填写整个闭环覆盖了 CRUD、表单提交、拦截器、聚合统计这些面试必问的点。很多 Java 学习者把它当课设或毕设蓝本原因很朴素SSM layui JSP MySQL 这套组合老练、源码简单、每一层都能讲清楚。我拆这个项目时最关心三件事数据库表如何设计才能同时撑起发布和统计、SSM 的 XML 配置到底在哪里挖坑、以及问卷数据在前端怎么展示成报表。这篇笔记按“是什么 → 怎么实现 → 怎么部署 → 怎么改进阶”的顺序写适合正在写 Java 课设、或者想从 SSM 过渡到 Spring Boot 之前先补一次手写分层的人。源码包里已经有 SurveyController、SurveyService、QuestionService 这些完整类数据库导入后基本可以直接跑我下面讲的每一步都能在工程里找到对应文件。2. SSM layui MySQL 的工程拆解分层、建表与配置文件2.1 为什么是 SSM 而不是 Spring Boot这套分层你能看到所有底层动作SSM 本质上是 SpringMVC Spring MyBatis 的组合SpringMVC 管请求转发、Spring 管对象装配和事务、MyBatis 管 SQL 映射。相比 Spring Boot 的全自动配置SSM 的所有动作都是“明牌”的你写了一个 Controller要自己把它扫描进容器你写了一个 Mapper 接口要自己配置 mapper 扫描你写了一个事务方法要自己告诉 Spring 哪条方法需要事务。这套问卷调查系统的源码里后端包结构一般是这样src/main/java/com/survey ├── controller // SurveyController、QuestionController ├── service // SurveyService、QuestionService ├── dao // SurveyMapper、QuestionMapper、OptionMapper ├── entity // Survey、Question、Option、AnswerRecord └── common // 分页、结果封装、拦截器等pom.xml 里的核心依赖是这套东西能跑起来的前提。常见做法是统一 Spring 版本为 5.xMyBatis 和 mybatis-spring 也要匹配否则启动时会报抽象方法错误。dependencies !-- spring -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.1.8.RELEASE/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.1.8.RELEASE/version /dependency !-- mybatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.2/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.2/version /dependency !-- mysql -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency !-- jsp/jstl -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies为什么要强调 pom 版本匹配因为这个项目是 Maven 项目IDEA 导入后会直接下载依赖。如果 Spring 版本是 4.x而 mybatis-spring 是 2.x启动就会遇到NoSuchMethodError而且错误信息很隐蔽不看完整堆栈根本找不到原因。这也是我在拆这种老项目时最喜欢保留 pom.xml 原样的原因改版本通常是给自己挖坑。2.2 数据库表怎么设计五张表支撑发布、填写、统计三个动作问卷调查系统的核心数据流是管理员建问卷 → 问卷下有题目 → 题目下有选项 → 用户提交后生成一份回答记录 → 统计时按题目和选项聚合。最稳的表结构是 5 张表加一张管理员表互相之间只用外键逻辑关联不用物理外键方便以后改字段。CREATE DATABASE survey_system DEFAULT CHARACTER SET utf8mb4; USE survey_system; CREATE TABLE t_admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), create_time DATETIME ) ENGINEInnoDB; CREATE TABLE t_survey ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT, status TINYINT DEFAULT 1 COMMENT 1草稿 2发布 3关闭, create_time DATETIME, publish_time DATETIME ) ENGINEInnoDB; CREATE TABLE t_question ( id INT PRIMARY KEY AUTO_INCREMENT, survey_id INT NOT NULL, question_type TINYINT DEFAULT 1 COMMENT 1单选 2多选 3文本, title VARCHAR(500) NOT NULL, sort_order INT DEFAULT 0 ) ENGINEInnoDB; CREATE TABLE t_option ( id INT PRIMARY KEY AUTO_INCREMENT, question_id INT NOT NULL, option_text VARCHAR(200) NOT NULL, sort_order INT DEFAULT 0 ) ENGINEInnoDB; CREATE TABLE t_answer_record ( id INT PRIMARY KEY AUTO_INCREMENT, survey_id INT NOT NULL, user_id INT, answer_content TEXT, create_time DATETIME ) ENGINEInnoDB; CREATE TABLE t_answer_detail ( id INT PRIMARY KEY AUTO_INCREMENT, record_id INT NOT NULL, question_id INT NOT NULL, option_id INT, answer_text TEXT ) ENGINEInnoDB;这里要特别解释一下t_answer_record和t_answer_detail为什么要拆开。answer_record存的是“谁在什么时间填了哪份问卷”一条记录代表一次提交answer_detail存的是“该次提交中每个题目的具体答案”。统计时以 detail 为主表关联 option 和 question就能算出每道题每个选项的票数。如果只放一张表用户提交多选题就会导致一行存多个 option_id统计 SQL 会写得非常痛苦。answer_content字段是冗余设计我一般建议保留。它的作用是当用户填文本题时可以直接把答案拼成一个长字符串存进去方便后续做关键词模糊查询做常规统计报表时不依赖它只当作兜底数据。2.3 关键配置Spring 容器、MyBatis 映射和 web.xml 怎么协同SSM 项目最常见的启动顺序是Tomcat 先读 web.xml加载 ContextLoaderListener让它去读 applicationContext.xml 初始化 Spring 容器再加载 DispatcherServlet让它去读 spring-mvc.xml把 Controller 层拉起来。两份配置文件职责不同别图省事合成一份不然后续排查“为什么 service 是 null”的时候会非常头疼。先看jdbc.properties这是所有数据库相关问题的源头jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/survey_system?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password123456然后是spring-mybatis.xml用 SqlSessionFactoryBean 扫描 mapper 文件bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.survey.dao/ /bean这里有个细节MapperScannerConfigurer的basePackage必须和MapperScan(com.survey.dao)指向同一个包否则 MyBatis 会生成代理类但 Spring 容器里仍然找不到。我遇到过不少项目Service 里Autowired一个 Mapper启动时不报错调用时才发现是 null原因就是 Mapper 接口没有被扫描进去。web.xml 里的 DispatcherServlet 配置决定了请求怎么进 Controllerservlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueutf-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern//url-pattern会把所有请求交给 SpringMVC这也意味着 JSP 页面里的静态资源 layui 的 js、css、图片都需要在 spring-mvc.xml 单独放行。后面的避坑章节我会专门说这一点。3. 核心链路代码管理员建卷、用户答卷、统计查询3.1 SurveyController 与 QuestionService一次“创建问卷 批量保存题目”的典型写法问卷调查系统的管理端最核心的操作是“一次提交同时生成问卷和它的全部题目”。前端表单通常用 layui 的动态行来实现一个题目对应一个题目标题下拉框和一组选项输入框最终把所有字段拼成同名数组提交。后端接收的核心是SurveyController的save方法。Controller RequestMapping(/admin/survey) public class SurveyController { Autowired private SurveyService surveyService; RequestMapping(/save) public String saveSurvey(HttpServletRequest request, Model model) { Survey survey new Survey(); survey.setTitle(request.getParameter(title)); survey.setDescription(request.getParameter(description)); survey.setStatus(1); // 草稿 String[] questionTitles request.getParameterValues(questionTitle); String[] questionTypes request.getParameterValues(questionType); String[] optionGroups request.getParameterValues(optionText); surveyService.saveSurveyAndQuestions(survey, questionTitles, questionTypes, optionGroups); return redirect:/admin/survey/list; } }这个写法的好处是 Controller 只做参数收集真正的业务逻辑全部下沉到 Service符合 SSM 项目的分层约定。SurveyService内部要做两件事插入问卷拿到自增 id再循环插入题目和选项这里用到了 MySQL 的useGeneratedKeys。public void saveSurveyAndQuestions(Survey survey, String[] titles, String[] types, String[] optionGroups) { surveyMapper.insert(survey); if (titles null) { return; } for (int i 0; i titles.length; i) { Question q new Question(); q.setSurveyId(survey.getId()); q.setTitle(titles[i]); q.setQuestionType(Integer.parseInt(types[i])); q.setSortOrder(i); questionMapper.insert(q); if (optionGroups ! null i optionGroups.length) { String group optionGroups[i]; if (group ! null !.equals(group.trim())) { String[] opts group.split(\\|); for (String opt : opts) { Option option new Option(); option.setQuestionId(q.getId()); option.setOptionText(opt.trim()); optionMapper.insert(option); } } } } }这里有两个参数说明。第一optionGroups前端传过来的是“每道题的选项拼接串”比如 非常满意|满意|一般|不满意Service 层用split(\\|)拆开再逐条插入选项表第二sort_order我直接用数组下标i这样前端题目显示顺序就是后端存储顺序不需要额外做排序字段维护。注意题型的第 3 种“文本题”不需要选项所以前端在optionText里传空字符串即可代码里用.equals(group.trim())跳过了插入选项。如果把空字符串也拆成数组会得到[]插入一条空选项统计报表里就会出现一个空白行这种问题很难一眼发现。3.2 用户端 JSP layui 表单单选、多选、文本题的提交协议用户端的问卷页面是基于 JSP 动态渲染的。后端把题目和选项数据放进 request 后转发给fill.jspJSP 里用 JSTL 的c:forEach循环生成表单项。form idsurveyForm action${pageContext.request.contextPath}/survey/submit methodpost input typehidden namesurveyId value${survey.id}/ c:forEach items${questionList} varq varStatusqs div classquestion-block p${qs.count}. ${q.title}/p c:if test${q.questionType 1} c:forEach items${q.optionList} varopt label input typeradio nameanswer_${q.id} value${opt.id}/ ${opt.optionText} /label /c:forEach /c:if c:if test${q.questionType 2} c:forEach items${q.optionList} varopt label input typecheckbox nameanswer_${q.id} value${opt.id}/ ${opt.optionText} /label /c:forEach /c:if c:if test${q.questionType 3} textarea nameanswer_${q.id} rows3/textarea /c:if /div /c:forEach button typebutton onclicksubmitSurvey()提交问卷/button /form这里最关键的约定是nameanswer_${q.id}也就是“每个题目的 input 的 name 都包含题目 id”。提交时前端拿到所有带answer_前缀的参数名通过 jQuery 序列化成一个对象再 POST 出去。function submitSurvey() { var answers []; $(input[name^answer_], textarea[name^answer_]).each(function () { var name $(this).attr(name); var value $(this).val(); answers.push({ questionId: name.replace(answer_, ), value: value }); }); $.ajax({ url: /survey/submit, type: POST, data: { surveyId: $(#surveyId).val(), answers: JSON.stringify(answers) }, success: function (res) { layer.msg(提交成功, { icon: 1 }); } }); }用户端 Controller 收到answers后解析 JSON 数组再一条条写入answer_detail。多选题天然会出现同一个 questionId 对应多条 value后端用循环插入即可这正好对应了前面表结构把 detail 拆出来的理由。有人会把多选题答案用逗号拼成一个字段虽然也能展示但统计时要用FIND_IN_SET或者LIKE去处理效率和可读性都差很多不建议。3.3 统计报表的 Service 层实现把选项编号换算成可展示的占比统计功能是整个项目里最体现 SQL 功力的地方。需求是“每个题目下每个选项被选了多少次”同时要支持单选和多选。单选可以直接按 option_id 分组多选时一行 detail 里只有一条记录也能按 option_id 分组所以两种情况可以共用同一套 SQL。select idselectStatistics resultTypemap parameterTypelong SELECT q.title AS questionTitle, o.option_text AS optionText, COUNT(ad.option_id) AS answerCount FROM t_question q LEFT JOIN t_option o ON q.id o.question_id LEFT JOIN t_answer_detail ad ON o.id ad.option_id WHERE q.survey_id #{surveyId} GROUP BY q.id, q.title, o.id, o.option_text ORDER BY q.sort_order, o.sort_order /selectService 层拿到这个 List 后不能直接丢给前端。因为前端要按“题目”维度分组而 SQL 返回的是平铺的每一行“题目选项数量”。我一般会在 Service 里做一次组装。public ListMapString, Object getStatistics(Long surveyId) { ListMapString, Object rows surveyMapper.selectStatistics(surveyId); MapString, ListMapString, Object grouped new LinkedHashMap(); for (MapString, Object row : rows) { String qTitle (String) row.get(questionTitle); grouped.computeIfAbsent(qTitle, k - new ArrayList()).add(row); } ListMapString, Object result new ArrayList(); for (Map.EntryString, ListMapString, Object entry : grouped.entrySet()) { MapString, Object item new HashMap(); item.put(questionTitle, entry.getKey()); item.put(options, entry.getValue()); result.add(item); } return result; }前端 JSP 只需要两层循环就能渲染出表格或者柱状图。如果要更直观可以在这个 List 基础上再算一次百分比但千万别在 SQL 里做百分比除法因为当总数为 0 时会返回 null前端展示会崩。4. 部署与常见问题排查JDK 1.8、Tomcat 与 MySQL 5.7 的实战笔记4.1 从 Navicat 建库到 IDEA 启动一次完整的部署流程这套项目的运行环境很明确JDK 1.8、Tomcat 7/8/9、MySQL 5.7、IDEA 或 Eclipse。我在本机复现时用的是 JDK 1.8 Tomcat 9 MySQL 5.7一次跑通。下面按步骤来每步都不要跳。第一步安装并启动 MySQL 5.7在 Navicat 里新建数据库survey_system字符集选utf8mb4然后右键“运行 SQL 文件”把源码包里的survey.sql导入进去。导入后刷新一下表看到 t_admin、t_survey 等表存在就说明成功了。MySQL 8 理论上也能用但驱动版本要改后面排查章节会说。第二步打开 IDEA选择“Open”选中项目根目录等待 Maven 自动导入依赖。确认 pom.xml 的依赖里没有红字报错。然后在 Project Structure 里把 Project SDK 设置为 JDK 1.8Language Level 设为 8。第三步修改jdbc.properties把jdbc.password改成你本地 MySQL 的密码。这是整个部署里最简单也最容易忘的一步十个翻车里有五个是这里。第四步配置 Tomcat。点击 Run 下拉框 - Edit Configurations - 左上角 号 - Tomcat Server Local在 Deployment 页签添加survey:war explodedApplication context 写/survey。注意一定要用 war exploded不要用 war否则 IDEA 每次要重新解包改一个 JSP 都要重启。第五步启动 Tomcat。如果能在控制台看到 “Depoying artifacts” 和 “Completed” 字样再访问http://localhost:8080/survey看到登录页就算跑通了。命令行方式部署也给你适合不想用 IDEA 内置 Tomcat 的人# 先把项目打成 war 包 mvn clean package -DskipTests # 复制到 tomcat 的 webapps 目录 cp target/survey.war /usr/local/tomcat/webapps/ # 启动 tomcat /usr/local/tomcat/bin/startup.sh这种方式不需要回到 IDETomcat 会自动解压 war 包并部署。缺点是想看日志只能去logs/catalina.out里翻不如 IDEA 的控制台直观。4.2 常见问题排查五个最容易击穿新手的点问题一数据库连接报Access denied for user rootlocalhost现象Tomcat 启动后日志里出现Access denied for user rootlocalhost (using password: YES)页面 500。原因jdbc.properties里写的密码和你本地 MySQL 的 root 密码不一致或者你的 MySQL 8 使用了caching_sha2_password认证插件而项目里的mysql-connector-java是 5.1.x 版本驱动不识别新认证方式。解决先在 Navicat 里用 root 连接一次确认密码。如果是 MySQL 8把 pom.xml 里的 mysql 驱动换成mysql-connector-java 8.0.11并在 jdbc.url 末尾追加serverTimezoneAsia/ShanghaiuseSSLfalse。如果是 MySQL 5.7直接用 5.1.47 驱动最稳。问题二启动后报ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet现象Tomcat 控制台直接抛ClassNotFoundException应用没有起来。原因IDEA 里 Tomcat 的 Artifact 配置没有把 Maven 库里的 jar 包打包进去。很多新手把项目导入 IDEA 后直接点启动没有先配置 Artifact 的lib目录。解决在 Project Structure - Artifacts - 选中项目 - 在 Available Elements 里把Maven: org.springframework:spring-webmvc:5.1.8.RELEASE等 jar 全部双击添加到 WEB-INF/lib 下。更快的做法是把 Artifact 类型改成Web Application: ExplodedIDEA 会自动带上依赖。问题三页面能打开但 layui 的 CSS、JS 全部 404现象浏览器 F12 看到http://localhost:8080/survey/static/layui/css/layui.css404页面完全裸奔。原因web.xml 里 DispatcherServlet 的映射是/它会接管所有请求包括普通静态文件。SpringMVC 默认没有处理静态资源的 Handler于是.css、.js请求全部走了 Controller 的映射自然是 404。解决在 spring-mvc.xml 里加一行静态资源放行配置。mvc:annotation-driven/ mvc:resources mapping/static/** location/static//如果你的项目里静态资源目录叫assets就把路径替换成assets/**。加了配置之后不需要重启 Tomcat只要重新部署 war exploded 即可。问题四表单提交中文全部变成?问号现象管理员新建问卷时标题里的“满意度调查”保存后变成????。原因数据库建库时是 utf8mb4但 JDBC 连接串没有指定字符集导致连接层用的默认字符集不是 utf8同时 Tomcat 接收 POST 请求时默认 ISO-8859-1中文被转成拉丁字符。解决检查两层一是 jdbc.url 里要有characterEncodingutf8二是 web.xml 里配置了上面写的CharacterEncodingFilter。这两个都检查完重新导入 SQL 文件再试。问题五统计报表里出现空选项或者多选题计数不准现象报表页显示一道题有 N 个空选项目或者多选题票数总和比提交人数多。原因空选项是因为题目创建时optionGroups[i]传了空字符串后端没有过滤干净多选题计数不准是因为每次提交后答案明细表里单选题和文本题的数据也不同统计时多表关联导致笛卡尔积。解决创建题目时后端循环体里必须先if (group null || .equals(group.trim()))跳过统计 SQL 里始终以t_answer_detail为主表、t_option为左表避免用INNER JOIN。多选题用户勾了几个选项就有几条 detail 记录这是正确设计不要把多选答案合并成一条。4.3 怎么确认系统真的跑通了访问一下这两个地址就行项目跑通后不要急着改代码先做一次端到端的冒烟验证。打开浏览器分别访问下面两组地址管理端http://localhost:8080/survey/admin/login 用户端http://localhost:8080/survey/survey/fill?surveyId1管理端登录后建一份包含单选、多选、文本题的问卷然后发布。再打开用户端地址以普通用户身份填一次问卷并提交。最后回到管理端的统计菜单如果能看到每道题每个选项的投票数量和占比这个系统的链路才算是真正闭环了。这一步很多人会跳过但恰恰是它最能帮你发现“Controller 转发路径写错”“Service 事务没提交”这类隐蔽问题。我一般会在确认完这个闭环之后再考虑给项目加新功能。5. 进阶改法给自己问卷系统加多题型和 Excel 导出5.1 多题型支持表结构和页面渲染的小改如果你拿到源码后想让它能在简历或面试里讲出更多东西第一件事就是支持多种题目类型。现有表结构已经有question_type字段前端 JSP 里我建议把单选项和工整的多选项解析在一块用一个 ECharts 柱状图来展示统计结果。ECharts 的 CDN 地址放在 JSP 页面的底部然后把后端统计结果转成 JSON 输出。5.2 用 POI 把统计结果导成 Excel一个可以抄的 Service 方法统计报表做出来后另一个常用诉求是导出 Excel。Apache POI 是可以直接依赖的库不需要额外的中间件。核心方法很直观创建一个HSSFWorkbook在 Service 里把统计结果循环写入 cell。public void exportStatistics(Long surveyId, HttpServletResponse response) { ListMapString, Object stats getStatistics(surveyId); HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(问卷统计); HSSFRow header sheet.createRow(0); header.createCell(0).setCellValue(题目); header.createCell(1).setCellValue(选项); header.createCell(2).setCellValue(票数); int rowIndex 1; for (MapString, Object data : stats) { HSSFRow row sheet.createRow(rowIndex); row.createCell(0).setCellValue((String) data.get(questionTitle)); row.createCell(1).setCellValue((String) data.get(optionText)); row.createCell(2).setCellValue(((Number) data.get(answerCount)).intValue()); } response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamestatistics.xls); try { workbook.write(response.getOutputStream()); } finally { workbook.close(); } }这里有个容易翻车的点HSSFWorkbook的write(response.getOutputStream())之后不能手动调用response.getWriter()否则会抛出IllegalStateException。导出按钮在 JSP 里直接是一个普通链接不需要走 AJAX不然浏览器不会触发下载。最后说一点我在拆这个项目时最深的第一人称教训一开始我以为把 SQL 文件导入数据库、再配好 Tomcat 就能跑结果花了一晚上排静态资源 404 和 java.sql.SQLException。从那以后我每次接手 SSM 老项目都强制先走一遍“建库 → 改 jdbc → 部署 → 填写一条问卷 → 看统计”这个闭环再决定要不要动代码。这个习惯帮我避开了至少三次因为环境问题导致的假 bug希望帮到你。本文还有配套的精品资源点击获取