简介:这是一套基于Spring Boot + Vue前后端分离架构的扶贫网站设计与实现项目,面向Java方向毕业设计、课程设计及期末大作业场景,也适合希望从零上手完整Web项目的初学者。资源共包含2020个文件,压缩包约300.15MB;其中84个Java文件构成后端业务逻辑,59个JS文件与38个XML配置支撑前端交互与系统配置,另有1个SQL数据库脚本和1个Word格式论文,以及多达1807个Markdown文档,便于阅读项目笔记与部署说明。项目代码完整、可直接运行,下载后既能作为毕业设计答辩项目,也可按需修改用于课设练手。包内除了源码和数据库,还提供了论文与大量说明文档,可帮助理解项目整体结构、前后端交互设计与部署运行流程,减少从零搭建的时间成本。目前已有627人学习/下载,适合需要完整实战项目参考的Java学习者。
1. 一个扶贫网站,为什么能同时难住后端、前端和数据库三拨人
每年这个时候,都有大量读者拿着同一个标题来找我——基于 Spring Boot + Vue 的扶贫网站。一开始我以为是个冷门选题,直到看到它的出现频率才意识到:这大概是高校软件工程类毕业设计里最稳的题目之一。它看起来就是一个典型的 CRUD 管理系统,但真正做完的人都知道,它卡人的地方从来不在业务复杂度,而在三个环节的衔接:Spring Boot 的接口规范、Vue 的组件通信、MySQL 的表关系设计。只要有一条线没捋顺,项目就跑不起来。
这篇笔记我会按一条完整落地路径来讲:先拆项目结构和数据库设计,再分别走通后端接口和 Vue 前端页面,最后是部署和避坑。读者里有第一次碰前后端分离的,也有被这个题目折磨得只剩一周时间的,照着这篇文章的代码和参数设置走,能把返工的可能性压到最低。
需要先说明的是,这类项目最常见的组织方式是三个部分:poverty-backend(Spring Boot 后端)、poverty-web(Vue 前端),外加一个 SQL 脚本。论文和源码包通常是对这三部分的文字化整理。我下面讲的所有内容,都按这个结构展开。
2. 先拆项目骨架:扶贫网站的功能边界与数据库设计
2.1 扶贫网站的典型角色与功能边界
扶贫网站和外卖系统、电商系统最大的区别在于:它的用户身份不是“游客”和“管理员”两种就够。我一般会设计三种角色,后台管理端、前台门户和移动端共用同一套后端 API 的设计方式:普通用户(贫困户或帮扶对象)、扶贫干部(村级审核人员)、系统管理员。
三个角色的核心诉求完全不同。普通用户需要在线提交帮扶申请、查看政策资讯、浏览扶贫项目进度;扶贫干部要处理辖区内申请单的初审和复核;系统管理员管的是用户管理、角色权限、新闻发布、项目分类和数据统计。在代码实现上,这三个角色对应 Spring Boot 里的不同 Controller 前缀,也对应 Vue 端的三种路由视图。
别一上来就急着写代码。我强烈建议先把权限模型定清楚,这是这套项目最容易返工的地方。常见做法是使用 RBAC 模型,建五张核心表:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。前端用 Vue Router 的导航守卫配合后端返回的菜单列表来控制页面可见性,后端用拦截器校验每个请求的 JWT Token 和接口权限。
2.2 核心数据表设计:字段、类型与状态流转
数据库是这种毕业设计项目里容易被低估的部分。很多人的 SQL 脚本只有七八张表,导致写论文时没有内容,开发时频繁改表结构。我一般会规划到 12 张表左右,既能覆盖完整业务,数量上又不显得刻意凑数。下面这些表是我认为可以保留的:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| t_user | 用户表 | id、username、password、real_name、phone、role_id |
| t_role | 角色表 | id、role_name、role_code |
| t_menu | 菜单表 | id、parent_id、menu_name、path、component |
| t_help_apply | 帮扶申请表 | id、user_id、apply_type、description、status、create_time |
| t_project | 扶贫项目表 | id、project_name、cover_image、fund_amount、progress |
| t_policy | 政策资讯表 | id、title、content、publish_time、view_count |
| t_volunteer | 志愿者活动表 | id、activity_name、start_time、location、max_people |
| t_help_comment | 帮扶留言表 | id、apply_id、user_id、content、reply_content |
特别要强调帮扶申请表里的 status 字段,这是整个业务流程的核心。我给它的值是 0(待审核)、1(审核通过)、2(已驳回)、3(已完成)。前端页面的按钮显示逻辑——提交、撤回到编辑状态、确认完成,全都要和这个状态值相对应。设计时千万别把状态设计成 varchar 存中文,那样排序统计和前后端对接都会难受。
2.3 关键权限思路与菜单表驱动路由
菜单表用 parent_id 做父子级,对应前端的多级菜单。一个常见的设计是:管理员进入后台是 layout 布局,普通用户进入的是门户主页,扶贫干部看到的是申请列表和统计图表。后端登录接口返回的数据结构是{ token, userInfo, menus },前端拿到 menus 后动态拼接路由。
这套设计在论文里也好写,讲清“用户角色权限对应关系”和“前端动态菜单渲染”,导师一般不会在这个部分挑刺。而如果把权限直接硬编码在前端,论文里就绕不开“如何保证接口安全”这个问题,答辩时会比较难圆。
3. Spring Boot 后端落地:从建表脚本到接口调试的最小可运行方案
3.1 建库建表 SQL 与全局配置:先把地基打对
我把 SQL 脚本放在项目根目录的sql/poverty.sql里。权限相关的三张表建议用下面的方式创建,字段类型和注释一次性写完整,后面能省掉大量改表的时间。
-- 用户表 CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(255) NOT NULL COMMENT '密码,MD5加密后存储', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(20) DEFAULT NULL, `role_id` int(11) DEFAULT NULL COMMENT '角色ID,关联t_role', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 角色表 CREATE TABLE `t_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_name` varchar(50) NOT NULL, `role_code` varchar(50) NOT NULL COMMENT '如 ADMIN/MANAGER/USER', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; -- 用户角色关联表 CREATE TABLE `t_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';这里值得多说一句的是表名的前缀和字符集。表名统一用t_前缀,后面加下划线分割单词,和 Java 实体类去掉前缀后的驼峰命名刚好对应。字符集建议直接用 utf8mb4,不要用 utf8。政策资讯里很可能有人粘贴一些特殊字符或表情符号,utf8 存不了,线上报错时排查又要浪费半天。
后端项目的初始目录结构推荐 maven 单模块,包名建议用com.poverty下面分controller、service、mapper、entity、config五层。这种结构是主流毕业设计里最常见的分包方式,对应的论文结构也好写。生成项目时用 Spring Initializr,版本选择 Spring Boot 2.7.x,对应的 JDK 8 或 11 都可以。
3.2 登录认证:JWT 拦截器 + 线程变量保存用户信息
登录是这套系统里第一个要动真格的地方。上一节里用户表的 password 字段要强调一下:直接用 MD5 加密存储是常见且可接受的毕设实现,项目里通过调用工具类MD5Util.md5(password)完成。能用 BCrypt 更好,但如果论文里没有对加密算法展开分析,MD5 的写法能让你少解释很多设计细节。
// JwtUtil.java —— 生成和解析Token的工具类 @Component public class JwtUtil { // 密钥和过期时间放在application.yml里,方便切换 @Value("${poverty.jwt.secret}") private String secret; @Value("${poverty.jwt.expire}") private Long expire; public String createToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }登录接口的 Controller 里,流程是接收前端传来的 username 和 password,查t_user,比对密码,然后生成 token 返回。前端拿到 token 后,存储在 localStorage 中,后续所有请求在 axios 拦截器里统一加到Authorization请求头。
注意 token 过期时间不要设得太短,这个项目里用户不会一直停留在页面上,180 分钟(即 expire=10800000)是一个接近实际使用的默认值。密钥随便写一个长字符串,不要用默认的 “secret”,容易被懂行的人在答辩时提一个问题。
3.3 申请与审核接口的实现:状态机的正确写法这里要强调一下:springboot 的接口路径和前端 axios 请求路径必须完全对齐,大小写和单词拼写一个都不能错。常见做法是把所有接口路径以常量形式放在前端 utils/request.js 里统一导出,避免前端写错,也方便后端改动时同步调整。这是前后端分离项目里翻车率最高的地方之一。
在 Controller 中,接收前端 JSON 数据使用 @RequestBody 注解,归还给前端的数据统一用一个 Result 对象包装,结构包含{ code, msg, data }。所以不要把个别接口单独返回 Map 或裸对象,前端拦截器只认这一个结构,统一了才好排错。
4. Vue 前端页面搭建:从 Element UI 到后台管理界面
4.1 用 Vue Admin 模板搭后台,还是自己写 CSS?这里要强调一下
在 vue 生态里,后台管理系统的主流方案是 vue-element-admin 模板。但完整版的模板对新手来说太重,光路由和权限的代码就够研究三天,我一般建议手动搭建一个简化版:Vue 2 + Vue Router + Vuex + axios + Element UI。这套组合是招聘市场上 Vue 岗位最常用的技术栈,也是互联网上最常见的技术栈之一,配套教程也全,对新手更友好。
创建项目用vue create poverty-web,选择 Vue 2 预设,注意不要勾选 Vue 3,因为 Element UI 对 Vue 3 的支持已经在 2022 年后逐渐转向 Element Plus,很多组件写法不兼容。项目结构按 views(页面组件)、router(路由配置)、api(请求封装)、store(状态管理)、components(公共组件)组织。
# 创建 Vue 2 项目 vue create poverty-web # 进入目录后安装 Element UI 和 axios、js-cookie npm install element-ui axios js-cookie --save # 安装 sass 依赖(用于样式编写) npm install sass sass-loader --save-dev这里的 sass-loader 版本要注意,Vue 2.6 对应的 sass-loader 是 10.x,装上 13.x 会在启动时直接报错,具体版本对应关系后面避坑章节里会说。
4.2 登录页与路由守卫:用户没登录就跳转
登录页不需要设计得太复杂,居中一张卡片,放用户名、密码、登录按钮,调后端/api/user/login接口,成功之后调用store.commit('SET_TOKEN', res.data.token)把 token 写入 Vuex 和 cookie。然后在路由配置文件里加上全局前置守卫,这是控制页面访问权限最关键的一段代码,没有它,用户直接在浏览器地址栏输入/admin就能绕过登录。
// router/index.js 路由守卫 router.beforeEach((to, from, next) => { // 白名单:不需要登录就能访问的页面 const whiteList = ['/login', '/register', '/'] if (getToken()) { // 已登录还去登录页,直接放行到首页 if (to.path === '/login') { next({ path: '/' }) } else { next() } } else { // 未登录访问其他页面,先检查是否在白名单 if (whiteList.includes(to.path)) { next() } else { next(`/login?redirect=${to.path}`) } } })这段代码用 getToken() 判断登录状态,实现后端的 token 校验。redirect 参数的作用是让用户登录成功后能跳转回他原本想去的页面,这个小细节在论文的“系统实现”部分可以当一个亮点来写。页面加载后,再在 mounted 钩子或全局初始化时请求用户信息和菜单权限列表,完成动态路由注册。
4.3 后台布局与申请管理的核心页面:表格 + 弹窗 + 分页
后台首页布局首选 Element UI 的 el-container 组件:左边是侧边栏菜单,右边是 header 和 main 区域。侧边栏用 el-menu 渲染,菜单项数据从后端动态获取,和后端的 t_menu 表一一对应。为了偷懒,也可以把菜单写死在前端,但如果论文里承诺做了动态权限,建议还是从后端拿,差别只在于一个接口和几行 v-for。
帮扶申请管理页面是这套系统里最核心的页面,按用户的诉求展示“我提交的申请列表”。后端分页接口返回的是Result对象包着 PageResult,PageResult 结构为{ total, records }。前端分页组件 el-pagination 要写对 current-change 事件,每次页码变化时重新请求接口,否则翻页没反应。
<template> <div> <el-table :data="applyList" v-loading="loading" border stripe> <el-table-column prop="applyType" label="申请类型" width="140" /> <el-table-column prop="description" label="申请说明" show-overflow-tooltip /> <el-table-column prop="status" label="状态" width="120"> <template slot-scope="scope"> <el-tag :type="statusTag(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag> </template> </el-table-column> <el-table-column label="操作" width="220"> <template slot-scope="scope"> <el-button v-if="scope.row.status === 0" type="text" @click="handleEdit(scope.row)">编辑</el-button> <el-button v-if="scope.row.status === 0" type="text" @click="handleDelete(scope.row)">撤回</el-button> <el-button v-if="scope.row.status === 3" type="text" @click="handleComment(scope.row)">填写反馈</el-button> </template> </el-table-column> </el-table> <el-pagination background layout="prev, pager, next, total" :total="total" :page-size="queryForm.pageSize" @current-change="handlePageChange" /> </div> </template>statusText 和 statusTag 是两个很常用的小函数,status 值为 0 时显示“待审核”加 warning 颜色,1 显示“审核通过”加 success 颜色,2 显示“已驳回”加 danger 颜色,3 显示“已完成”加 info 颜色。这里的前端展示逻辑必须和后端字段值一一对应,前后端分离项目调试的大头之一就是状态值对不上号——后端改了一个数字,前端忘了同步,页面就显示乱了。
4.4 前端项目结构整理与二次开发思路
前端开发到后面,会积累出几个反复用的东西:封装好的 request.js 里统一处理 401 状态码;一个组件PovertyUpload.vue封装图片上传;表格页面上提取公共列表组件。我的建议是,文件命名统一用功能名,页面或组件别叫 index.vue,叫ApplyList.vue、PolicyDetail.vue这类,否则后期在几十个文件里找页面会让人抓狂。
前端答辩时,导师通常问得最多的问题是“你如何实现权限控制”。标准的回答思路是:后端返回用户角色与菜单树,前端在 router.beforeEach 里判断 token,进入页面后根据菜单渲染侧边栏。如果被追问“前端的按钮怎么控制”就补充 v-permission 自定义指令。这能让导师快速确认代码确实是按项目实际需求做出来的。
5. 常见坑与排查:版本冲突、端口占用、跨域、字段对不上
5.1 Java 与 Spring Boot 版本冲突:启动直接报错
现象:使用 Spring Boot 3.x 或 Spring Boot 2.4 以上高版本,或 Java 17 环境,项目明明代码没问题,启动却报 “Unsupported class file major version” 或各种 JAXB 类找不到的异常。
原因:Spring Boot 高版本和旧版依赖的兼容性容易出问题。特别是 javax.* 还是 jakarta.* 这个分水岭,在 Spring Boot 3.x 里已经全面切换到了 jakarta 命名空间,老文章的代码复制过来直接就编不过。而 Java 17 环境下用 Spring Boot 2.3 搭配旧版本 mybatis-spring-boot-starter,也会出现奇怪的注解扫描问题。
解决:版本选择保守方案。Java 8 或 11 搭配 Spring Boot 2.7.x,mybatis-spring-boot-starter 采用 2.1.x,mysql-connector-java 用 8.0.26 以下版本。application.yml 里配置数据源时,driver-class-name 要写com.mysql.cj.jdbc.Driver,不要写com.mysql.jdbc.Driver,后者在 MySQL 8 下直接连不上。
5.2 前端 npm install 失败与 sass-loader 版本冲突
现象:项目跑不起来,npm run serve报一串 webpack 相关的语法错误,或者安装依赖时出现大量红字gyp ERR。
原因:sass 和 sass-loader 版本错配的情况非常多。Vue CLI 4 内置的 webpack 版本是 4.x,装 sass-loader 12 以上的版本会要求 webpack 5,项目就会带不动;而系统 node 版本过高时,old sass 又编译不过。
解决:锁定版本组合,在 package.json 里显式写成sass-loader@10.1.1和sass@1.32.13,删除 node_modules 重新安装。如果是 Node 18 环境,建议优先用nvm use 16降级。这里的核心教训是锁依赖版本,不能靠网上博客说的“最新版就能用”来过日子。
5.3 MySQL 8 连接报错:Public Key Retrieval 与时区问题
现象:后端启动后访问任何接口都报 “Public Key Retrieval is not allowed” 或The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。
原因:MySQL 8 缓存密码插件需要客户端配合获取公钥,同时 Server 端时区字段在 JDBC 连接里如果不指定会解析出乱码。
解决:JDBC 连接串上追加三个参数。
url: jdbc:mysql://localhost:3306/poverty_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true这串参数基本可以消掉一大半 MySQL 8 的连库报错。注意不要为了省事去掉 useSSL=false,特别是本地开发环境,去掉能少很多警告。
5.4 跨域问题:前端连不上后端接口
现象:前端 axios 请求一直挂在 pending 状态,或者浏览器 Console 报 “Access-Control-Allow-Origin” 错误。另一种情况是直接报 404,但后端 Swagger 或浏览器直接访问接口地址明明能通。
原因:开发环境下,前端跑在 8080 端口,后端跑在 9090 端口,两边域名和端口不同,浏览器默认拦截跨域请求。如果 404 而接口地址又没问题,通常是 Spring Boot 项目没有配置 context-path,前端请求路径多了一层和少了一层对不上导致的。
解决:后端写一个 CORS 配置类最省事,不要用注解逐个 Controller 加。在 Spring Boot 中配置全局跨域,同时把前端 request.js 的 baseURL 统一为后端完整前缀。另外把后端的 context-path 配置成一个明确的前缀,比如/api,前端所有请求都走这个前缀,路由看起来也清爽。
5.5 文件上传路径重启即丢失:图片存哪里更稳
现象:上传的帮扶材料图片在本地测试正常显示,重启 Spring Boot 再访问时就 404 了。
原因:开发时把文件保存到了项目运行目录下的相对路径,比如./upload,重启后该目录被清理或路径被改变,导致引用失效。如果把路径写死在src/main/resources下,打包成 jar 运行后文件根本写不进去。
解决:把上传路径配到 application.yml 的poverty.upload-path项,指定一个项目外部的绝对路径,例如 Linux 下的/data/poverty/upload,Windows 下的D:/poverty_upload/。前端访问静态资源时,由后端映射一个资源处理器,addResourceHandlers把/files/**映射到实际路径。这个做法同样要写进论文,因为“文件存储位置设计”是答辩时的高频问题,能答得清楚这一个点,基本能证明你对项目的掌握程度。
6. 组合打包与验收要点:把 springboot+vue 项目部署上线
6.1 前后端打包:Vue 构建产物放进 Spring Boot 的两种方式
开发调试完成后,要考虑给导师演示时的一个核心问题:部署环境里很可能没有 Node 环境,怎么让导师双击就能跑起来?常见有两个方案。
第一,前端 npm run build 构建出 dist 文件夹,里面是打包好的 html、js、css 静态文件。把这个 dist 目录直接复制到后端项目的src/main/resources/static/下,然后重新打包后端为 jar。Spring Boot 会自动把 static 目录作为默认静态资源根目录,这样后端启动时就同时提供了接口服务和前端页面。这种方式的好处是部署简单,一个进程搞定,适合低配置演示环境。
第二,分别部署。后端打一个 jar,前端 dist 交给 Nginx 做静态资源映射,同时 Nginx 配置反向代理把/api请求转发到后端服务。这种方式是实际生产的标准做法,Nginx 还能配置负载均衡。但演示场景里突然多出来一个 Nginx 服务,一旦服务器没有安装,或者配置写错了,当场就会尴尬。我建议先用方案一应付交付和预演,再在论文里写方案二作为生产建议,既稳妥又显得专业。
6.2 打包后端 jar 前必须检查的配置清单
打包之前有四个地方非常容易被跳过:数据库连接串里有没有正确填演示机器的 MySQL 账号密码、JWT 密钥是不是还用的默认值、上传文件的保存路径、后端服务的端口号有没有被占用。这些配置全部集中在 application.yml 文件里,用一个表格检查即可。
| 配置项 | 本地开发建议值 | 部署演示建议值 | 注意事项 |
|---|---|---|---|
| server.port | 9090 | 9090(不变) | 演示机若 9090 被占用,改用 8080 |
| context-path | /api | /api | 前后端请求路径全靠它对齐 |
| spring.datasource.url | localhost:3306/poverty_db | 实际 MySQL 地址 | 部署机 Mysql 必须用 utf8mb4 |
| poverty.jwt.secret | 自定义长字符串 | 自定义长字符串 | 不要留默认值,答辩前改掉 |
| poverty.upload-path | ./upload | /data/poverty/upload | 存在项目内部会重启丢文件 |
6.3 验收自查:按业务流程把每个状态走一遍
部署完成不代表项目就算做完,答辩之前我习惯按完整业务流程做一轮验收,重点在状态流转的连贯性上。用管理员账号登录后台,新建一个用户,分配扶贫干部角色;再用扶贫干部账号登录,查看待审核列表,审核通过一条待审核的申请;然后换普通用户账号,确认这条申请状态已经变成“审核通过”。这一步看似简单,却能把登录认证、接口权限、状态更新、前端列表刷新这一整条链路全部测到位。
第二步验证权限。普通用户访问管理后台的相关路由,前端应该直接跳回首页或提示无权限;直接调用管理接口地址时,后端应该返回 401。如果这两处有一个能绕过,接口权限就是摆设,论文里这段写什么都会被质疑。
第三步验证基础功能。政策资讯发布一条带图片的内容,看图片能否正常展示;修改个人资料后,刷新页面看信息是否保持更新;上传帮扶材料后重启后端,看上传文件是否仍然能访问,这一步验的是之前 5.5 的坑有没有避开。
6.4 答辩准备:把实现过程讲成技术决策过程
整套系统做下来,我最深的一个感受是:答辩时不要讲“我做了哪些功能”,要讲“我在实现中遇到了什么问题,为什么这样解决”。把避坑部分讲好,比把所有功能罗列一遍更让技术导师认可。
比如导师问为什么要用 JWT 而不用 Session,可以从“前后端分离下 Session 需要处理跨域 Cookie,JWT 无状态更适合多端复用”来回答,结合你的登录代码里 token 的传递方式展开;问为什么需要状态字段,可以讲从 0 到 3 的四态流转,以及前端按钮如何随状态变化;问数据库为什么这么设计,可以讲 2.2 的角色与菜单表如何支撑动态权限。
最后再提醒一个实操层面的习惯:项目里所有配置项不要散落在代码里,统一往 application.yml 塞。数据库连接串、上传路径、JWT 密钥这些都要加注释写清楚。我早期做这类项目吃过配置文件不统一、到处写死参数的亏,后来养成了把所有常量集中在配置文件的习惯,至少在毕业设计这类交付场景里,这能帮你少接很多临时改需求的电话;也真心希望这篇笔记能把你的上线过程推得顺畅一点,希望帮到你。
如果把上面 6 章的要点连起来看,其实就是一条简单的主线:用 MySQL 把角色和状态定义清楚,用 Spring Boot 把接口和权限稳定输出,用 Vue 把数据和流程呈现出来。这个项目真正值钱的不是代码量,而是你对“前后端分离应用从头到尾如何协同”的完整认知。耐心跑通一遍,远比收藏一堆碎片化教程有用。
本文还有配套的精品资源,点击获取