基于微信小程序与Spring Boot的文旅宣传系统实践与解析

发布时间:2026/10/20 17:01:49
基于微信小程序与Spring Boot的文旅宣传系统实践与解析 做这类小程序Spring Boot的文旅宣传项目我前后接触过好几个河湟文化旅游景点宣传系统算是里面比较典型的一个。河湟地区的历史底蕴和民族文化特色非常鲜明景点分散、类型多样面向的游客群体又偏年轻化用微信小程序做宣传入口比纯网页或者公众号H5都要顺手得多。这个项目我在实际开发中完整走过一遍从需求梳理、技术选型、数据库设计到前后端联调、部署上线踩了不少坑也沉淀了不少经验。这篇就把整个系统的设计思路、核心实现和调试经验一次讲透给要做类似文旅小程序的朋友一个可以直接参考的完整方案。1. 项目设计拆解河湟旅游宣传为什么选小程序这套组合1.1 景区宣传的痛点与小程序的优势河湟地区不是单一景点而是由多个文化遗址、民俗村落、宗教建筑、自然风光组成的集合型旅游区域。做宣传系统之前我们和当地的文旅部门做过一轮调研发现传统宣传方式有几个绕不开的问题第一信息更新慢。纸质手册、官网专题页这些形式景点开放时间一变、临时闭园或者新增体验项目很难及时同步游客到了现场才发现跑空。第二展示形式单薄。河湟文化里最有价值的是历史故事、民族手工艺、建筑细节光靠几张图和几段文字根本讲不透游客扫码看个简介就走了留不下深度印象。第三互动能力几乎为零。传统宣传是单向信息输出游客没有渠道收藏感兴趣的景点、提交游览反馈文旅部门也拿不到用户偏好数据后续运营没有抓手。微信小程序恰好能补齐这些短板。它的分发逻辑是即用即走用户扫个码、搜个名字就能打开不需要下载安装十分贴合游客在景区现场的碎片化使用场景。而且小程序的开放能力很成熟定位、地图导航、微信支付、订阅消息都能直接调用省去了很多自建底层功能的成本。在项目里我们最后确定的技术路线是前端用原生微信小程序后端用 Java Spring Boot数据库用 MySQL接口通过 RESTful API 对接管理端做成网页后台。为什么不用前后端分离的 Vue Spring Boot 或者直接用 uni-app 跨端我们讨论过这个问题。原生小程序虽然写起来啰嗦但胜在稳定各种 API 调用最直接不需要处理多端兼容的额外复杂度。河湟宣传系统本身页面数量有限核心就是展示、搜索、收藏、留言几件事原生语法完全够用没必要引入跨端框架增加心智负担。管理端因为操作频率高、信息密度大用网页实现最合理小程序里只做游客面向的内容。1.2 系统功能边界用户端能做什么管理端管什么这个系统不是单纯的景点信息展示而是围绕游客游览前、游览中、游览后三个环节设计功能闭环。游客在小程序端能做的事包括浏览轮播推荐位和景点分类入口按文化遗址、民俗体验、自然风光等分类查看景点列表进入景点详情页查看图文介绍、开放时间、门票信息、交通指引和语音讲解收藏感兴趣的景点形成自己的心愿单在详情页底部提交留言或游览感受在个人中心看到自己的收藏记录和浏览足迹通过搜索框快速查找景点或关键词。管理端对应的是内容维护和运营能力管理员登录后可以维护景点分类和景点信息管理轮播图发布文化活动公告处理游客留言查看收藏量、浏览量的基础统计数据。这个后台做得不算特别重但足够管理人员日常更新内容了毕竟宣传系统的核心是内容保鲜后台好不好用直接影响运营效率。这里有个设计上的取舍值得说一下我们没有在小程序里做完整的在线购票和酒店预订功能只保留了门票信息的展示和咨询入口。原因很简单河湟区域内不同的景点分属不同运营主体票务系统各自独立短时间不可能统一接口。强行做购票功能反而会让系统背上复杂的订单和支付逻辑拖慢上线节奏。宣传系统的定位就是把游客引过来、看得懂、想去看购票是后续生态的事不能一步到位。2. 技术框架与核心架构解析2.1 后端技术选型与层级设计后端用 Java Spring Boot版本我们选了 2.7.x 系列。这个版本兼容性强社区资源多遇到问题在网上基本都能查到解决方案对中小型项目来说是很稳妥的选择。项目结构按经典的三层架构来组织src/main/java/com/huanghe/travel/ ├── controller/ // 接口层接收请求、参数校验 ├── service/ // 业务逻辑层处理具体业务流程 ├── mapper/ // 数据访问层MyBatis-Plus 接口 ├── entity/ // 数据库实体类 ├── dto/ // 数据传输对象避免直接暴露实体 ├── config/ // 配置类比如跨域、拦截器、WebMvc ├── common/ // 通用类统一返回结果、异常处理 └── utils/ // 工具类JWT、日期处理等这种分层的好处是职责单一controller 只负责接收参数和返回结果业务判断都收敛在 service 层后续改需求的时候不会牵一发动全身。比如景点详情的浏览量加一这个操作在 service 里做就很自然controller 不用关心。ORM 选择了 MyBatis-Plus主要看中它的代码生成和条件构造器。这个项目里单表查询占了绝大多数MyBatis-Plus 的 LambdaQueryWrapper 写起来非常简洁比如按分类查景点列表ListScenicSpot list scenicSpotMapper.selectList( new LambdaQueryWrapperScenicSpot() .eq(ScenicSpot::getCategoryId, categoryId) .eq(ScenicSpot::getStatus, 1) .orderByAsc(ScenicSpot::getSort) );省去了大量手写 XML 的工作。遇到多表联查的情况比如景点详情要同时带出图片和标签我们就在 mapper 里写自定义 SQL结合 PageHelper 做分页性能足够用。JWT 做身份令牌是当前的主流做法。用户在小程序端授权登录后后端生成 token 返回给前端小程序把 token 存在 storage 里后续请求放到 header 中。服务端写一个拦截器统一校验 token除了登录和首页列表这两个接口放行其余都要求携带合法 token。这种无状态鉴权方式非常适合这种轻量级系统不需要在服务端维护 session也方便后续做接口扩展。2.2 小程序端架构和目录组织小程序端没有用任何第三方框架就是微信官方原生的 WXML、WXSS、JS配合小程序原生组件和 API。目录结构是这样的miniprogram/ ├── pages/ │ ├── index/ // 首页轮播分类推荐 │ ├── spotList/ // 景点列表页 │ ├── spotDetail/ // 景点详情页 │ ├── food/ // 美食推荐页 │ ├── culture/ // 民俗文化活动页 │ ├── mine/ // 个人中心 │ ├── favorite/ // 我的收藏 │ └── search/ // 搜索页 ├── components/ // 自定义组件如图片轮播 ├── utils/ │ ├── request.js // 封装 wx.request统一处理 token │ └── config.js // 接口地址配置 ├── app.js ├── app.json └── app.wxssutils/request.js这个小封装很有必要它统一处理了请求头拼接、token 过期跳转、错误码提示这些逻辑页面里不用每次重复写 wx.request 的那一坨回调。基础版本大概长这样const request (url, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token) wx.request({ url: baseUrl url, method, data, header: { Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 200) { if (res.data.code 200) resolve(res.data.data) else if (res.data.code 401) { wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject(res.data.msg) } else { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data.msg) } } else { reject(请求失败) } }, fail: (err) reject(err) }) }) }首页的轮播图、分类宫格、推荐景点列表都通过页面 onLoad 里并行发请求拉取。这里有个小技巧首页多个接口同时请求时可以用Promise.all合并等待减少白屏等待时间。不过如果项目数据量大了建议还是单独维护一个聚合接口减少请求往返。2.3 数据库设计要点与表结构关系数据库设计是这个项目里最需要提前想清楚的环节。文旅景点宣传系统的核心实体是景点围绕景点延伸出分类、图片、评论、收藏这些关联数据。表结构设计如下去掉了无关字段t_admin管理员表字段包括 id、username、passwordMD5 加密存储、real_name、last_login_time。t_user用户表字段包括 id、openid微信唯一标识、nickname、avatar、phone、create_time。openid 是最关键的字段小程序每次登录都会拿到不同的 code后端用 code 换 openid如果查不到就自动注册。t_category景点分类表字段包括 id、name、icon、sort、status。对应文化遗址、民俗体验、自然风光、美食小吃等分类。t_spot景点表这是整个系统的主表字段相对多id category_id // 关联分类 name // 景点名称 cover_image // 封面图 intro // 简短介绍 content // 富文本详情 address // 详细地址 open_time // 开放时间 ticket_price // 门票价格 traffic_info // 交通指引 audio_url // 语音讲解地址 longitude // 经度配合地图定位 latitude // 纬度 status // 上下线状态 sort // 排序权重 view_count // 浏览量 create_timet_spot_image景点图集表字段包括 id、spot_id、image_url、sort。一个景点对应多张图片方便详情页图片画廊展示。t_favorite收藏表字段包括 id、user_id、spot_id、create_time。联合唯一索引uk_user_spot防止重复收藏后续统计收藏量直接 count 这个表。t_comment评论留言表字段包括 id、spot_id、user_id、content、parent_id支持回复、create_time、status。t_banner轮播图表字段包括 id、image_url、link_type、link_param、sort、status。运营人员可以随时调整首页轮播不用改代码。t_announcement公告表字段包括 id、title、content、publish_time、status。在设计景点表时我特别加了一个audio_url字段。河湟文化中有大量口述史、民间传说、民俗仪式光用文字展示不够生动语音讲解能显著提升游客的沉浸感。这个音频文件可以存放在云存储上数据库只存 URL这样表结构保持简洁资源扩展也不受限制。另外关于经纬度字段看起来不起眼但实际价值很高。景区小程序如果能提供地图上的景点分布和定位导航游客会方便很多。项目里虽然第一版没做地图集成但这个字段提前留了后面接地图 SDK 就不用改表。3. 核心功能与交互细节实现3.1 微信登录与 Token 鉴权完整流程微信小程序的登录逻辑是这个系统的入口也是很多新手容易搞混的地方。正确流程是第一步小程序端调用wx.login获取临时 code。这个 code 五分钟内有效且只能使用一次必须立即发给后端。wx.login({ success: async (res) { const code res.code const loginRes await request(/api/user/login, POST, { code }) wx.setStorageSync(token, loginRes.token) } })第二步后端收到 code 后调用微信的接口凭证服务用 code 换取 openid 和 session_key。这里需要 appid 和 appsecret配置在 application.yml 里。第三步根据 openid 查用户表存在则直接登录生成 token不存在则自动创建新用户。这里注意新用户第一次进入小程序时头像和昵称是拿不到的需要后续引导用户在小程序内手动授权填写不能依赖登录时返回的数据。Token 的生成我用的是 JWT把 userId 和 openid 放进 token 里设置 7 天有效期。游客使用小程序的频率不会特别高有效期太短会导致频繁重新登录影响体验太长又有安全风险7 天是折中值。有一个容易忽略的细节接口层统一接收 token 后拦截器里解析出 userId 存入 ThreadLocal后续业务逻辑直接使用。这样 controller 和 service 不用反复传 userId 参数代码干净很多。项目里 ThreadLocal 的清理一定要在 finally 里做否则 Tomcat 线程复用时会串数据。3.2 景点列表与详情页的数据联动景点列表页面对的典型场景是游客点了一个分类看到该分类下所有上线状态的景点卡片往下滑动可以加载更多点击卡片跳转到详情页。这里有个交互细节值得强调小程序页面间传参用 URL 拼接是最简单的方式但景点 id 这种参数传过去了详情页再回头重新发起请求获取最新数据才能保证数据第一时间准确。有些偷懒的写法会把景点标题和封面图片直接通过事件传过去页面会闪一下又跳回体验很差而且如果列表数据更新了详情页拿到的还是旧数据。我的建议是列表页只传 spotId所有展示数据都靠详情页自己拉取。景点详情页是内容展示的核心阵地。为了把河湟文化的内容讲透详情页的布局分了几个区块基本信息区展示名称、地址、开放时间和门票价格。图片画廊区用 swiper 组件轮播景点多角度的实拍图并在图片底部增加小圆点指示器。介绍内容区展示富文本图文内容这个富文本从小程序端展示有讲究服务端返回的内容如果是 HTML需要用rich-text组件解析但rich-text不支持部分标签样式图片自适应宽度、段间距这些都要靠内联样式兜底。音频导览区展示语音讲解按钮点击播放音频文件这个功能对体现文化旅游深度帮助极大。交互按钮区是收藏和留言入口。收藏按钮的状态要和当前用户是否已收藏数据进行联动页面加载时同时拉取景点详情和收藏状态两个接口。onLoad(options) { const spotId options.id this.getSpotDetail(spotId) this.checkIsFavorite(spotId) }其中checkIsFavorite接口返回布尔值用于给收藏按钮切换样式。收藏动作本身做成防抖避免用户手滑连续点击导致重复请求。3.3 收藏与评论模块的业务逻辑收藏功能虽然简单但在代码设计上有几个容易出错的地方第一收藏接口要校验景点是否存在且处于上线状态否则用户收藏了一个已经下架的景点后续访问详情会得到不友好的空页面。第二收藏接口建议设计成幂等操作即用户重复点击收藏只插入一次通过唯一索引兜底。第三取消收藏返回的数据结构要和收藏时保持一致方便前端统一处理逻辑。评论功能相对复杂一点。游客可以在详情页提交评论也可以对别人的评论进行回复。数据库设计上通过parent_id区分根评论和子评论。评论展示的时候默认查根评论并按时间倒序每条根评论下面嵌套查它的子评论。这里有一个性能和体验的平衡问题如果一次性把某个景点下的所有评论包括子评论全查出来数据量大了页面会卡但如果每条根评论都单独查一次子评论数据库请求次数又多。我们这个项目采用两步方案第一步查根评论分页第二步根据这一页根评论的 id 集合用IN查询把所有子评论一次性查出来然后在内存中组装成树形结构。这样数据库请求次数控制在两次代码复杂度也可以接受。评论内容需要做关键词过滤吗严格来说作为开放的游客留言功能还是加一层简单的敏感词过滤更好我们在后端维护了一个关键词列表评论提交时做包含判断命中的词替换成星号避免审核压力。3.4 后台管理的核心页面与接口管理端是一个独立网页用 Vue Element UI 快速搭建。它和小程序端共用同一套后端接口但接口路径上做了区分小程序端用/api/user/开头管理端用/api/admin/开头后端有对应的拦截器做不同的权限校验。后台最核心的页面是景点管理页左侧是景点分类树右侧是对应分类下的景点表格。表格里的操作按钮包括编辑、上架/下架、删除。编辑页面是一个大表单字段比较多分块组织基础信息、图文详情、图片上传、音频上传。图片上传使用云存储的直传方式前端拿到文件后先传给后端接口换取临时凭证再直传到对象存储避免所有文件都经过后端转发减轻后端带宽压力。轮播图管理页做了一套简单的可视化排序通过拖拽调整轮播顺序排序值实时保存到数据库。这个功能看似简单但运营人员非常依赖因为首页效果直接影响游客的第一印象。公告管理页用于发布文化活动通知、临时闭园公告等。发布后小程序首页的公告栏就会展示最新的三条。这个模块逻辑不复杂但要注意公告的时效性后台列表里要有状态标识过期的公告自动置为失效不再展示。数据统计页展示了基础的数据看板今日新增用户、总用户数、景点收藏总数、评论总数以及每个景点的浏览量排行 Top 10。在第一版里只做柱状图和表格展示不涉及复杂的报表分析但已经能给运营决策提供有效参考。4. 运行调试与部署实录4.1 本地环境搭建与启动流程本地运行这套系统需要准备的基础环境是JDK 1.8 或 11、Maven 3.6、MySQL 5.7 及以上版本、微信开发者工具。整体启动流程我整理成了一份操作顺序照着做基本不会有问题。第一步用前端脚手架生成一个 Spring Boot 项目Java 版本选 1.8依赖引入 Spring Web、MyBatis-Plus、MySQL Driver、Lombok、JWT 相关包。Spring Boot 2.7 搭配 MyBatis-Plus 3.5.x 是成熟稳定的组合。第二步创建数据库hehuang_travel字符集用 utf8mb4因为要存储表情符号和生僻字河湟地区的民族词汇里部分字在 utf8 下会乱码。执行项目里的 SQL 脚本自动建表并插入初始数据。第三步改application.yml重点核对数据库账号密码、微信小程序配置的 appid 和 secret。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hehuang_travel?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 wx: appid: 你的小程序appid secret: 你的小程序secret第四步启动后端服务访问http://localhost:8080/admin/index确认管理端可以正常打开。第五步用微信开发者工具导入小程序前端项目在utils/config.js里把 baseUrl 改成http://localhost:8080前提是开发者工具里开启了不校验合法域名选项否则本地调试时请求会被拦截。第六步在小程序端模拟登录检查控制台有无报错。如果能正常拉取首页数据并完成登录说明整个链路已经打通。4.2 常见问题排查与解决方案开发过程中我踩过的坑不少挑几个最有代表性的讲。第一个是跨域问题。小程序真机调试时请求的是 HTTPS 正式域名而本地开发时请求的是 localhost 或局域网 IP。Spring Boot 后端必须配置适合小程序的跨域策略否则小程序端会报url not in domain list或者请求被浏览器拦截。小程序真机的合法域名校验一定要在开发期间就设置正确或者在开发者工具里临时关闭校验。后端跨域配置用 WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二个是图片加载不出来。小程序端的图片对域名有要求正式上线必须使用 HTTPS且域名要配置到小程序后台的 downloadFile 合法域名里。如果是本地开发图片 URL 是带有端口的 http 地址开发者工具里需要关闭合法域名校验才能显示。我在实际开发里建议把图片统一放到对象存储开发阶段用测试域名上线前换正式域名这样图片切换只改一个配置不用动代码。第三个是 MyBatis-Plus 的分页失效。这是因为分页拦截器没有注册。MyBatis-Plus 3.4 之后分页插件需要手动配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }如果没加这个配置Page查询返回的总数永远是 0列表页的加载更多功能直接失效。第四个是微信登录时后端请求微信接口超时。微信接口请求需要用专门的服务端调用不能让它走正常的业务超时设置。我们在项目中单独维护一个微信 HTTP 客户端设了单独的连接超时和读超时参数连不上时给出明确提示避免阻塞整个登录接口。第五个是富文本内容在小程序端显示格式错乱。这个问题的根因是rich-text组件对标签支持有限像section、自定义的 class 样式都会被忽略。我们的解决方案是后端在录入内容时严格控制格式只允许基础标签段落、图片、链接图片全部转成 base64 或完整 URL避免相对路径在移动端打不开。4.3 部署上线的一些经验项目从开发到上线部署环境差异带来的问题往往比开发阶段更隐蔽。我们用的是云服务器加域名的方式部署微信浏览器要求小程序请求的接口域名必须是 HTTPS 且备案过的这一步前置条件不解决好后面所有功能都白搭。部署流程大致是这样先在服务器上配置好 Java 环境和 MySQL 数据库导入正式环境的 SQL 脚本。然后把项目打成 jar 包用java -jar启动。为了管理方便推荐使用 Systemd 写一个服务文件这样进程崩溃后能自动重启日志也有统一的地方看。域名和证书配置方面在 Nginx 里做反向代理把 443 端口的 HTTPS 流量转发到后端服务。HTTP 请求全部 301 重定向到 HTTPS同时配置好静态资源的缓存策略。小程序端正式环境的 baseUrl 必须是https://你的域名/注意这个 URL 末尾的斜杠很有讲究后端如果做了上下文路径或者 Nginx 做了路径转发斜杠缺失会导致路径拼接错误。数据库上线前要做一次完整的数据清理开发期间的测试数据全部清掉并且给关键表加上合适的索引。比如景点表的 category_id 和 status 字段组合索引评论表的 spot_id 和 create_time 索引。不加索引的情况下数据量一上来详情页的评论加载就会明显变慢。另外备份策略不能少。我习惯每天凌晨用 crontab 自动备份数据库保留最近 7 天的备份文件。小程序系统的内容维护频率高万一运营误删数据或者代码升级出了兼容性问题备份能救命。5. 实际操作中的一些体会这套系统从立项到交付我最深的体感是文旅宣传系统看起来功能简单但内容运营的分量远比功能开发重。景点、文化活动、民俗故事这些信息是持续变化的系统把展示位、管理流程、审核机制都搭好了才有后面的内容保鲜。谁也不想看到一个宣传景区的小程序里最新一条公告还是半年前的。开发过程中有一个环节值得反复确认就是小程序端的审核细节。微信小程序的类目选择和信息录入要提前查清楚文旅宣传类有没有额外的资质要求不同类目对应不同的审核规则如果等开发完再提交审核发现类目不对改起来会非常被动。再说一个容易被低估的功能点搜索。景区宣传系统的搜索不能只做景点名称精确匹配用户往往是记不全名字才来搜的。比如用户搜青稞他可能想找的是青稞酒坊或青稞文化体验馆搜古堡可能对应的是好几个景点。在搜索接口里加一层关键词模糊匹配加同义词联想用小表存搜索词库效果提升很明显。第一版别急着上 ES数据库 LIKE 加词库扩展中小规模完全够用。最后再分享一个小技巧景点详情的浏览量统计不要每次都去更新数据库的 count 字段这样高并发下会有锁竞争。可以在 Redis 里维护一个键值对每次访问详情就INCR一次定时任务每十分钟把累计值同步回 MySQL。项目规模小不用做得特别重但这样顺手的设计能避免以后流量上来后的隐患。这个项目做完之后我最大的收获不是技术上的突破而是理解了技术要为内容服务这句话的含义。小程序只是一种媒介形式真正吸引游客的是里面呈现的文化深度和游览体验。后面如果继续迭代我倾向在语音讲解的互动性上做文章比如加入地图导览点位的自动触发或者根据游客当前位置推荐附近的景点和特色美食。遇到同样在做文旅宣传项目的朋友欢迎一起交流实际经验这套系统里可以扩展的玩法还有不少。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询