简介面向计算机专业毕业设计的智慧乡村旅游服务平台小程序资源包基于微信小程序、SSM 框架与 MySQL 数据库构建涵盖管理员、用户、商家三类角色完整覆盖旅游景点管理、路线规划、订单处理、我的收藏、账户充值、购物车、我的订单等前后台核心功能。包内共 895 个文件压缩后约 40.48MB以 vue/java/js 源码为主体包含大量前端页面组件与后端业务类同时配有图片与图标素材、JSON/XML 配置文件、SQL 数据库初始化脚本以及毕业论文文档和 MP4 演示录屏还提供安装、运行、构建的 bat 脚本便于快速启动整套项目。目前已有 313 人学习下载适合需要毕业设计参考、小程序全栈实践或希望基于 SSM 框架进行二次开发的读者。压缩包内文件按源码、数据库、论文与视频分类归档论文覆盖系统分析、可行性分析、功能设计与数据库设计能直接对照源码梳理完整实现链路是一份可用于课程设计或毕业设计的完整蓝本。1. 智慧乡村旅游小程序一套SSM微信小程序的毕设方案值不值得做毕业季打开选题库你很可能刷到过“智慧乡村旅游服务平台小程序”这类题目前端是微信小程序后端是SSMSpringSpringMVCMyBatis数据库是MySql。它比单纯的后台管理系统多了一个“小程序端”的移动场景对于想体现完整项目的毕设来说能覆盖注册登录、景点展示、民宿/特产下单、管理员维护数据这条完整业务链。你搜“微信小程序ssmmysql源码”时看到的那些项目包通常还带数据库脚本、毕业论文和视频演示——这套组合能不能落到你手里跑起来才是关键。适作者包括准备Java毕业设计的在校生、想给学生做课设案例的指导老师以及想快速上手SSM小程序协作的初学者。2. 功能与数据库设计先拆模块再建表10张表撑起乡村旅游平台2.1 业务闭环拆解游客、商家、管理员三个角色各要什么拿到“智慧乡村旅游服务平台”这个题目第一件事不是打开IDE而是把用例画清楚。我一般先按角色拆需求游客也就是正在某个乡村景区玩的人他要能浏览景点列表、查看景点详情和图文介绍能看到民宿和特产能下订单能在个人中心看到自己的历史订单。为了体现“智慧”还可以加一个“智能路线推荐”实现方式可以朴素按景点热度排序配上用户收藏记录推荐。游客登录用微信小程序授权登录后端用SSM完成session和token管理。商家就是录入民宿/特产的运营人员他需要发布商品或房源、修改库存、查看订单、处理订单状态。在毕设答辩里商家和管理员权限经常合并到“后台管理端”你只需要做两种角色普通用户游客和管理员管理员往下兼任商家功能。管理员是后台系统的核心负责维护景点分类、轮播图、订单审核、评论删除、数据统计。数据统计别贪多完成“每日订单量、各订单状态占比、本月营业额”三个小图表就够了。把业务闭环画出来后后台接口的数量就清楚了用户登录、景点列表、景点详情、民宿列表、特产列表、订单创建、订单列表、订单状态更新、评论提交、管理端CRUD。对应到SSM后端大约十个Controller这个工作量对毕设来说刚好不空也不至于做不完。2.2 数据库表怎么建核心表结构、字段类型与索引这样设我通常建议用10张表左右支撑这个平台表太多答辩说不清太少又显得模块不完整。核心表是这样一组用户表(t_user)、管理员表(t_admin)、景点分类表(t_category)、景点表(t_scenic)、民宿表(t_hotel)、特产表(t_goods)、订单表(t_order)、订单明细表(t_order_item)、评论表(t_comment)、轮播图表(t_banner)。下面给一张核心订单表的建表SQL其它表按同样的设计习惯来CREATE DATABASE IF NOT EXISTS rural_travel DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE rural_travel; CREATE TABLE t_order ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT 订单id, order_no VARCHAR(32) NOT NULL COMMENT 订单编号业务上全局唯一, user_id INT NOT NULL COMMENT 下单用户id关联t_user.id, hotel_id INT DEFAULT NULL COMMENT 关联民宿id预订民宿时使用, goods_id INT DEFAULT NULL COMMENT 关联特产id购买特产时使用, goods_count INT DEFAULT 1 COMMENT 购买数量民宿订单填1, total_price DECIMAL(10,2) NOT NULL COMMENT 订单总金额单位元, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待付款 1已付款 2已完成 3已取消, create_time DATETIME NOT NULL COMMENT 下单时间, pay_time DATETIME DEFAULT NULL COMMENT 支付时间, remark VARCHAR(255) DEFAULT NULL COMMENT 用户备注, INDEX idx_user (user_id), INDEX idx_status (status), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB COMMENT 订单表;这段SQL里有几个参数是毕设演示时的关键点字符集采用utf8mb4而不是utf8因为用户评论里可能出现生僻字和Emojiutf8存4字节字符会报错引擎用InnoDB保证订单表和订单明细表能跨表查事务状态字段status用TINYINT而不是String枚举值在Java里定义常量前端用数字映射文字这样排序和统计都省事。order_no用唯一键约束业务上生成规则一般是用时间戳加随机数或UUID短串保证演示时连续下单不冲突。t_user表里字段不一定要存openid你可以把openid放t_user表但注意openid是第三方标识不要把它当主键主键用自增idopenid加唯一索引。t_scenic表记得留image_url和content长文本字段景点详情页要展示多图中的首图后台富文本编辑器发出来的HTML片段就存在content里。t_goods表需要stock字段下单时先查库存再扣减演示时可以让库存为0的特产显示“已售罄”这是一个很加分的细节。2.3 MySQL初始化字符集、时区、root连接串的常见配置数据库导到你电脑上时如果只有.sql脚本你要先在本地跑通MySQL再导入。第一步是安装MySQL这个过程经常成为毕设第一道坎。我一般建议装MySQL 8.0安装时选utf8mb4默认字符集root密码记牢端口用默认的3306如果你电脑上已经装了5.7也完全能用只是连接驱动和SQL语法略有差异。导入脚本最常见的方式是命令行mysql -u root -p rural_travel.sql如果提示ERROR 2002 (HY000): Cant connect to local MySQL server through socket ...说明MySQL服务没启动Linux上用systemctl start mysqldWindows上去服务管理器启动。另一个高频问题是时区在MySQL连接串后加serverTimezoneAsia/Shanghai否则JDBC连接时8.0驱动会报The server time zone value йʱ is unrecognized这个报错很折磨人看到乱码别慌就是时区没对上。字符集和时区属于MySQL安装配置教程里最容易被跳过的两行但这两行能解决一半的连接类报错。3. SSM后端搭建SpringSpringMVCMyBatis的最小可运行工程3.1 为什么这个毕业设计还用SSM而不是Spring Boot很多拿到源码的人第一句话是怎么不用Spring Boot原因很现实不少学校毕设题目模板还停留在SSM教学大纲也把SpringMVCMyBatis当作考查点。SSM的配置方式虽然繁琐但配置即学习你在web.xml里能看到请求是怎么进DispatcherServlet的在applicationContext.xml里能看到Bean是怎么被管理的答辩时每一个组件都能讲出“为什么在这”。SSM框架的黄金时代虽然过去但对做课设/毕设的人来说它比Spring Boot更接近“手写框架”的底层逻辑导师问起原理你能答得上。这不是说SSM更好而是这个题目语境下SSM足够。它没有Spring Boot的自动配置所以每一步都需要显式声明新手出问题时反而容易通过报错堆栈定位。真正需要注意的边界是SSM工程需要外置Tomcat运行pom.xml里多包版本容易冲突这也是下一小节要解决的。3.2 三个核心配置web.xml、applicationContext.xml、mybatis-config.xml搭建SSM工程时web.xml是入口它负责加载Spring容器和DispatcherServletweb-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:springmvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mapping filter filter-nameencodingFilter/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-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-app关键参数有两个load-on-startup设成1让DispatcherServlet随容器启动初始化避免第一次请求时才开始加载SpringMVCurl-pattern//url-pattern把所有请求交给SpringMVC处理但.jsp和静态资源也要放行否则/images/xx.jpg会被DispatcherServlet拦截导致图片加载不出来。常见做法是把静态资源映射单独加在springmvc.xml中。applicationContext.xml中最重要的就是数据源和SqlSessionFactorycontext:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classcom.mchange.v2.c3p0.ComboPooledDataSource property namejdbcUrl value${jdbc.url}/ property namedriverClass value${jdbc.driverClass}/ property nameuser value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialPoolSize value5/ property namemaxPoolSize value20/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath*:com/rural/mapper/*.xml/ property nameconfigLocation valueclasspath:mybatis-config.xml/ /bean mybatis:scan base-packagecom.rural.dao/C3P0连接池的参数按毕设体量调成initialPoolSize5、maxPoolSize20足够别写成几百浪费内存不说答辩时被问“为什么是这个值”你会不好答。mybatis:scan是MyBatis-Spring提供的扫描标签它会自动把com.rural.dao下的接口代理成Mapper。mybatis-config.xml则放一些通用配置最重要的是开启驼峰映射configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings typeAliases package namecom.rural.pojo/ /typeAliases /configurationmapUnderscoreToCamelCase设为true后数据库字段user_id能自动映射到Java属性userId不用再写一堆resultMap。如果你从源码里看到这个配置被删了几乎可以断定查询普遍为null。3.3 一个景点接口的完整链路Controller → Service → Mapper XML看源码最快的路径不是从头读而是挑一个最小接口从浏览器的URL一路追到SQL。我用“景点列表”这个接口说明链路长什么样。Controller层Controller RequestMapping(/api/scenic) public class ScenicController { Autowired private ScenicService scenicService; RequestMapping(value /list, method RequestMethod.GET) ResponseBody public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer pageSize) { PageResultScenic data scenicService.pageQuery(page, pageSize); return Result.success(data); } }Controller上的ResponseBody让返回的Java对象通过Jackson序列化成JSON。注意RequestParam(defaultValue 1)如果不加默认值小程序端请求/api/scenic/list不带page时会报400很多前端错误就是这么来的。Service层负责事务和分页逻辑最简单的是用PageHelperService public class ScenicServiceImpl implements ScenicService { Autowired private ScenicMapper scenicMapper; Override public PageResultScenic pageQuery(Integer page, Integer pageSize) { PageHelper.startPage(page, pageSize); ListScenic list scenicMapper.selectList(); PageInfoScenic pageInfo new PageInfo(list); return PageResult.build(pageInfo); } }PageHelper是SSM搭配MyBatis时最常用的分页插件PageHelper.startPage(page, pageSize)后面必须紧跟第一条查询语句中间不能插别的逻辑这是它作为拦截器生效的规则。Mapper接口和XMLpublic interface ScenicMapper { ListScenic selectList(); }mapper namespacecom.rural.dao.ScenicMapper select idselectList resultTypecom.rural.pojo.Scenic SELECT id, scenic_name, image_url, address, hot_level, description FROM t_scenic ORDER BY hot_level DESC /select /mapperresultType写实体类的全限定名前提是mybatis-config.xml里配置了typeAliases否则就要写全名。这里有个细节查询字段列表和数据库真实字段必须一致哪怕少一个descriptionJavabean对应属性就是null。反过来多查一个不存在的字段MyBatis倒不会报错只是浪费一点IO。3.4 Maven依赖版本匹配别让不同版本的包各说各话SSM最经典的坑是版本冲突。spring-beans如果是5.xspring-webmvc却拉到4.x运行时会报各种奇怪的ClassNotFoundException。我给毕设项目固定一个骨架版本族properties spring.version5.1.9.RELEASE/spring.version mybatis.version3.5.3/mybatis.version mybatis-spring.version2.0.3/mybatis-spring.version mysql.version8.0.19/mysql.version /propertiesSpring各模块统一用同一个版本MyBatis和MyBatis-Spring要按官方对应列表选mybatis-spring 2.0.x要求MyBatis 3.5MySQL驱动用mysql-connector-java的8.0.x配合MySQL 8.0数据库。另外javax.servlet-api建议用provided作用域打包时不会把Tomcat自带的servlet-api打进去否则部署到Tomcat可能冲突。依赖管理是SSM的黑匣子之一排错的通用办法是看启动日志第一条异常里有没有“Caused by”这个才是真正出问题的依赖。查看依赖树可以用mvn dependency:tree如果看到两个不同版本的spring-core那就是传递依赖打架了用exclusions排除旧版本即可。4. 微信小程序端登录、请求封装与首页渲染4.1 wx.login换token小程序登录的正确姿势智慧乡村旅游服务平台的微信小程序端第一步就是登录。很多人把wx.login得到的code直接当登录凭证用其实code有效期5分钟且只能换一次拿它直接当token是毕设答辩时的常见败笔。正确流程是login() { wx.login({ provider: weixin, success: (res) { if (res.code) { wx.request({ url: ${apiBase}/api/user/login, method: POST, data: { code: res.code }, success: (r) { const token r.data.data.token; wx.setStorageSync(token, token); } }); } } }); }后端拿到code后调微信的jscode2session接口得到openid和session_key用openid查用户表不存在则自动注册存在就直接登录。为了安全不要把小程序的session_key返回给前端而是后端生成一个自己的token比如UUID存到Redis或数据库里。我在毕设里见过最简单的token方案就是把它写进用户表的token字段请求时前端带上后端查表比对。演示时够用也不用过度设计。关键是wx.request的header要带上Authorization: token没有带的话后端拦截器会直接401前端就卡在首页加载不出来。4.2 封装request.jsbaseURL、状态码、带参上传统一处理小程序每个页面都调wx.request会写大量重复代码而且不好统一处理登录过期。我习惯在utils/request.js里封装一层const BASE_URL http://localhost:8080/rural-server; function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { const code res.data.code; if (code 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(new Error(登录过期)); } else if (code 0 || code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(new Error(res.data.msg)); } }, fail: (err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request, BASE_URL };BASE_URL在开发时可以填http://localhost:8080但真机上不能填localhost要填电脑的局域网IP比如http://192.168.1.100:8080/rural-server。这里还有微信小程序的硬规则正式版小程序要求请求域名必须配置到小程序管理后台的合法域名里而且不支持IP、不支持HTTP明文只有在开发者工具勾选“不校验合法域名”才能跳过。毕设演示一般用开发者工具跑这个问题不大但要提前知道它存在。关于请求状态码我建议后端返回结构统一成{code: 0, msg: 成功, data: ...}code为0表示成功。小程序端把它和HTTP状态码解耦这样后端报错时能更有效地拿到msg并展示给用户。4.3 首页列表渲染图片域名、自定义导航栏和富文本的三个坑首页往往是这个小程序的门面最容易出问题的三个点分别是图片、顶部导航栏、富文本。图片问题通常来自后台录入的图片URL是相对路径或外域地址小程序端image组件的src必须用完整URL且要求图片域名在downloadFile合法域名中开发者工具可跳过。最好的做法是后台存相对路径前端拼接BASE_URL path。顶部导航栏高度是另一个高频报错来源机型不同状态栏高度不同自定义导航栏时如果写死navigationStyle: custom后顶部留白不足内容会刘海屏遮挡。我一般用wx.getSystemInfoSync()获取statusBarHeight再用胶囊按钮位置动态算导航栏高度而不是写死44px。富文本详情页小程序端的rich-text组件对后台传来的HTML支持有限style里的部分CSS不生效图片可能无法自适应。如果后端用的是UEditor之类生成HTML我通常让后台只存纯文本和图片地址前端用rich-text渲染纯文本内容图片单独用image组件加载并开启modewidthFix这样图片宽度不会溢出。5. 避坑手册联调阶段最容易翻车的5个现场SSM加小程序这套组合出问题的高发区其实非常固定。下面五条是我在多个毕设项目里反复见到、也亲手修过的血泪现场每一条都按“现象→原因→解决”的顺序写你对照着排错比漫无边际地改代码快得多。5.1 MySQL 8.0连接报Public Key Retrieval is not allowed现象按照网上教程配好jdbc.properties后后端启动报SQLNonTransientConnectionException提示Public Key Retrieval is not allowed。页面所有接口都500后台日志只有这一条。原因MySQL 8.0默认身份认证插件是caching_sha2_passwordJDBC连接时客户端如果没有allowPublicKeyRetrievaltrue服务端不能通过非加密连接发送公钥。这不是你密码错了而是连接协议没协商好。解决在jdbc连接串中显式加两个参数useSSLfalse和allowPublicKeyRetrievaltrue。完整一行是jdbc:mysql://localhost:3306/rural_travel?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai。注意allowPublicKeyRetrieval在公网环境有安全风险本地毕设无妨但答辩时如果被问到能说出“仅用于本地开发展示”这句话就够了。如果你用的是新下载的MySQL Community Server 8.0也可以把用户改回mysql_native_password插件但不建议动它加参数更干净。5.2 真机请求失败但开发者工具正常域名合法性和TLS版本现象开发者工具里所有接口正常一到真机预览所有请求request:fail后台甚至没有收到请求。原因微信小程序真机环境强制校验request合法域名。开发者工具默认勾选了“不校验合法域名”能访问http://192.168.x.x:8080真机不行。即便你配置了合法域名后端必须是HTTPS并且SSL证书有效且不能是自签名证书。解决三条路。第一条演示用开发者工具这是最稳的但要提前在工具里勾选“不校验合法域名”。第二条把后端部署到带HTTPS的云服务器并配置备案域名这对毕设成本高不推荐。第三条用内网穿透把本机HTTP映射成临时HTTPS域名再把临时域名加入小程序后台的request合法域名演示完立即关闭别一直挂着。不管哪条都要知道演示前一小时把所有域名和端口测一遍别临上台改配置。我的习惯是开发者工具演示为主手机截图放论文为辅真机不是毕设必须。5.3 查询返回null但数据库有记录驼峰映射没开现象列表接口返回的JSON中所有下划线字段对应属性都是null比如order_no变成orderNo属性是null。字段少还不会报错看起来就是某个值丢了。原因MyBatis默认映射是列名和Java属性名严格相等order_no不能直接匹配orderNo。这个坑在SSM框架中非常经典因为Java规范用驼峰MySQL命名习惯用下划线。解决打开mybatis-config.xml在settings里加setting namemapUnderscoreToCamelCase valuetrue/。同时确认实体类的userId、orderNo这些字段名和JavaBean规范一致。另一个隐蔽点是如果你在resultMap里手写了映射驼峰配置在某些情况下会被resultMap覆盖所以尽量别在一个项目里混用两种映射方式。源码拿回来后第一步就检查这里能节省半天查错时间。5.4 日期格式变成一串带T和ZJackson时区和格式配置现象接口返回JSON中日期是2024-06-01T12:00:00.00000:00前端时间控件显示英文和UTC时间与本地时间差8小时。原因SpringMVC默认由Jackson序列化DateJackson默认输出ISO-8601格式且时区用的是UTC。数据库里create_time存DATETIME没问题但序列化时没按北京时间格式化。解决一种方案是在实体类的createTime字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)一劳永逸。另一种在springmvc.xml里配置全局Jackson的ObjectMapper设置dateFormat为yyyy-MM-dd HH:mm:ss、timeZone为Asia/Shanghai。我一般推荐全局配置这样所有Date字段统一格式不会出现有的接口格式化、有的没格式化。另外如果前端需要传日期到后端建议前端传字符串后端用DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)接收。这个坑属于“不报错但不正常”类答辩前最好把每个接口的返回JSON都过一眼别让评委看到乱码般的日期。5.5 演示视频录到一半token过期token过期与静默续期现象演示功能时前十分钟一切正常突然点击下单就跳回登录页重新登录后历史数据还在但演示流程断了。原因后端把token过期时间设为2小时或30分钟而毕设答辩很可能提前登录过或者演示时间拉长。如果后端在单次会话里直接让token失效且前端没有续期逻辑就会这样。解决在request.js的success回调里遇到code 401时不直接跳登录页而是先调用一次静默登录拿到新token后重试原请求。代码大致如下if (code 401) { silentLogin().then(() { request(options).then(resolve).catch(reject); }).catch(() { wx.navigateTo({ url: /pages/login/login }); }); return; }这样即使token过期用户只会感觉稍等半秒业务流程不中断。同时建议把后端token有效期设为24小时对本地演示足够如果使用JWT注意服务端时钟不能偏差过大否则签发和校验都对不上。演示视频录制前重新登录一次、重启后端让所有环境处于“新鲜”状态这个习惯能避免大量临场问题。6. 让毕设从能跑到能答辩验证技巧、演示脚本与论文配图习惯6.1 用Postman做一轮完整接口预演答辩前心里有底别等答辩当天才用手点页面强烈建议提前把核心接口按业务流程在Postman里存成一个Collection。顺序是管理员登录、维护景点、游客登录、查看列表、提交订单、更新状态。每个请求记录下预期返回值和实际返回截图这张表直接可以作为“系统测试”章节的素材。Postman里环境变量把baseUrl配好从笔记本搬到台式机也能一键切环境。6.2 演示视频录制顺序先跑业务再讲代码亮点演示视频一般5到8分钟。我惯用的顺序是先打开小程序从游客视角走一遍“浏览景点→查看民宿→下单特产→查看订单”中间把鼠标停留2秒让评委看清每个页面然后切换到后台管理端展示数据变化特别要展示新增一个景点后小程序端马上能看到。讲代码时挑2个亮点一个是登录token流程一个是订单事务。不要通读源码评委想听的亮点是“你如何解决了一个问题”。6.3 论文里的三张关键图ER图、系统架构图、时序图论文里最容易被扣分的点是图太少。至少要画三张图数据库ER图用PowerDesigner或Navicat生成后调整布局、系统架构图小程序端→SpringMVC→Service→MyBatis→MySQL、典型业务时序图以登录或下单为例。时序图我习惯用Visio或draw.io画画完放到“系统设计”一章答辩时点着图讲逻辑会清晰很多。我自己的血泪经验是答辩前一周把所有功能按真实业务走三遍录屏存好再准备一份关键报错的排查笔记。很多同学栽在“明明演示了一遍没问题”结果答辩当天数据库服务没启动第一眼就失去了好感。记住把你的后端启动命令、数据库导入命令、小程序AppID切换方法写进README让下一个打开你电脑的人也能五分钟跑起来。希望帮到你祝你的智慧乡村旅游小程序顺利通过答辩。本文还有配套的精品资源点击获取