0. 引言
做厦门周边游小程序这个项目,最初的核心诉求其实很简单:帮来厦门旅游的人快速找到周边值得玩的路线、景点和本地特色。但落地到技术上,它就是一个典型的“微信小程序前端 + Node.js后端”组合。小程序端负责展示地图、景点列表、路线推荐、用户登录和收藏,后端则通过Node.js提供接口,管理景点数据、用户信息和收藏记录。
之所以选Node.js而不是Java或Python,主要原因是这个项目体量不大,前端小程序和后端接口可以共用JavaScript语言栈,开发效率非常高。尤其是做景点搜索、路线推荐这类偏IO密集型的业务,Node.js的异步非阻塞模型非常合适。再加上Express这个轻量框架,几天时间就能把整套接口跑起来。微信小程序的生态这几年已经非常成熟,从用户登录、支付到消息推送都有现成能力,对接起来并不费劲。
这篇博文会完整拆解这个项目的实现过程,从Node.js后端环境搭建、Express接口设计、小程序端页面开发,到前后端联调时那些坑爹的问题。重点讲清楚每一步为什么这么做,以及我实测踩过的坑。如果你手上正打算做一个“小程序 + Node.js”的旅游类或本地生活类项目,这篇内容可以直接当参考。
1. 整体设计思路:为什么是“小程序 + Node.js”这个组合
1.1 技术选型背后的理由
单纯从小程序端来看,微信官方其实提供了云开发能力,不用自建服务器就能搞定后端。但我在这个项目里坚持用了自己的Node.js服务器,原因有三个。
第一,数据自由度。云开发的数据库虽然方便,但导出、迁移、多环境管理都比较受限。周边游平台后续要接第三方的景点数据、酒店库存、天气接口,这些数据进来之后要做清洗和聚合,放自己服务器上更好操作。第二,接口的灵活性。景点搜索的排序规则、路线的推荐算法、用户行为的埋点上报,这类逻辑放到后端接口里做,比在小程序端做要干净得多。页面只管渲染,业务逻辑全交给API。第三,部署可控性。Node.js服务部署到云服务器上,配合PM2做进程守护,在整个开发调试阶段响应速度非常直观。我本地起服务,小程序开发工具里直接连局域网IP,改完代码热重载,整个联调链路非常顺。
后端我用了Express 4.x,没上NestJS这类重框架。这个项目就十几个接口,用Express的中间件机制处理登录鉴权、参数校验、日志记录,完全够用。再加一个mysql2连接池操作MySQL数据库,简单直接。
1.2 数据库设计:三张核心表,别贪多
很多刚做小程序的人一上来就设计十几张表,其实对于周边游平台这个体量,三张核心表就够了。
用户表(users)存openid、昵称、头像、创建时间。景点表(spots)存名称、简介、图片、坐标、评分、门票类别。收藏表(favorites)存用户ID和景点ID的关联关系。如果要加路线功能,再加一张routes表,但核心架构不变。这种设计的好处是表关联简单,接口写起来不绕,后续要扩展评论、订单功能时,再加表也不会影响现有结构。
我用Sequelize做ORM,其实用原生SQL也行。但考虑到项目里要写几个联表查询,比如“查询我收藏的景点列表”,Sequelize的include语法能省不少事。
1.3 小程序端项目结构
小程序端我用的原生框架,没用uni-app。原因是这个项目只针对微信小程序一个端,原生框架调试最直接,而且小程序原生的组件和API文档最全,遇到问题搜索答案也容易。
整个前端结构分四块:页面、组件、工具库、请求封装。页面包括首页、景点列表、路线推荐、个人中心;组件是景点卡片、搜索栏这类可复用单元;工具库存放坐标转换、格式化时间这类函数;请求封装统一处理wx.request,带token、处理错误码。
2. 核心细节解析:Node.js后端环境搭建与接口开发
2.1 Node.js安装与环境配置的坑
为什么单独拎出来说环境配置?因为我在这个项目上浪费的时间,一半都花在环境问题上。很多新手在Windows上装完Node.js,打开命令行执行npm命令,直接报错:npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
这个问题的原因很简单:Windows的PowerShell执行策略默认是Restricted,禁止运行.ps1脚本。解决办法有两个:一是以管理员身份打开PowerShell,执行Set-ExecutionPolicy RemoteSigned命令,然后选Y确认;二是不用PowerShell,直接用CMD(命令提示符)来跑npm命令。我个人推荐第二个方案,换个环境比改系统策略更省事。
Node.js安装本身没什么难度,去官网下载LTS版本,一路下一步就行。关键点是安装路径最好不要有中文和空格,否则后续某些工具可能出问题。你可以在命令行输入node -v和npm -v确认安装成功,如果都能输出版本号,说明Node.js环境没问题。另外我习惯配置npm的国内镜像源,执行npm config set registry https://registry.npmmirror.com,下载依赖包的速度会快很多,特别是项目中需要安装express、mysql2这类依赖时。
2.2 Express接口开发的核心逻辑
后端接口的设计,我遵循了一个很朴素的原则:接口只做资源的管理和业务逻辑的处理,不掺入任何页面的渲染逻辑。整个后端我拆成了三层:路由层、控制器层、数据访问层。路由层只管URL转发,控制器层处理业务判断和参数校验,数据访问层负责查数据库。
举个例子,做一个“获取景点列表”的接口。路由层注册一个GET /api/spots的接口,控制器层接收query参数(如city、type、page),校验参数后调用数据访问层的查询方法,最后把数据返回给前端。这个分层的好处是以后接口数量变多时,不会出现所有逻辑挤在一个文件里的情况。
我做的第一个版本,接口路径设计是小程序端固定的。比如登录用POST /api/login,景点列表用GET /api/spots,景点详情用GET /api/spots/:id,收藏操作是POST /api/favorites,取消收藏是DELETE /api/favorites/:spotId。接口路径的意义不仅是给前端调用,也是项目结构清晰度的体现。
在做景点列表接口时,有一个很实用的细节:分页参数要规范化。前端传page和pageSize,后端做限制,pageSize最大不超过50,这样能防止有用户一次性拉全量数据把服务器拖垮。还有,所有接口都返回统一的JSON格式,包括code、message、data三段,小程序端的请求封装只用判断code字段就能确定成功还是失败。
2.3 微信登录与用户身份体系的实现
微信小程序的登录流程,已经有一套标准模式了。前端通过wx.login获取一个临时code,传给后端;后端拿着这个code去微信服务器换openid和session_key。openid是这个用户在当前小程序下的唯一标识,session_key用于后续解密敏感数据。
这个流程中有个问题很常见:小程序端明明调了wx.login,但后端用code换取openid时报错,提示code无效。这个问题百分之八十是code已经被使用过了。wx.login生成的code只能用一次,且有效期只有五分钟。有些开发者会在多个地方同时调用wx.login,导致后一次获取的code覆盖了前一个,传给后端时就已经失效了。正确做法是:在小程序启动时只调一次wx.login,拿到code后立刻传给后端,后端处理完把openid和自定义登录态(token)返回给前端,前端把token存在storage里,后续所有请求都带上这个token。
我做的方案是后端生成一个token,用jsonwebtoken库签发,有效期设七天。存到MySQL的users表里也行,但我当时用了JWT的无状态方案,用户信息直接编码在token里,后端只需要验证签名就能解析用户身份。这种方案的优点是不用频繁查库,缺点是token一旦签发,在有效期内无法主动失效。对于周边游这种低频登录场景,完全够用。
3. 小程序端实操:从页面搭建到接口联调
3.1 顶部导航栏与页面布局
小程序顶部导航栏的高度,是一个很隐蔽的适配问题。不同机型的导航栏高度不一样,如果你的页面里有自定义导航栏,就必须动态获取状态栏高度和导航栏高度,否则在全面屏手机上布局会错位。
我处理的方案很成熟:在页面onLoad里调用wx.getWindowInfo()获取statusBarHeight,然后根据胶囊按钮的位置算出导航栏高度。实测下来这个方案在iOS和Android上都很稳定。如果没有自定导航栏的需求,直接用微信默认的navigationStyle就行,省很多适配工作量。
这个项目的首页布局相对常规:顶部是搜索框,下面是景点分类的横向滚动栏,再往下是推荐景点列表。搜索框用的是原生input组件,点击搜索跳转到景点列表页,并带上搜索关键词参数。景点列表页接收参数后,向后端接口发起请求,返回匹配的景点数据。
3.2 景点卡片组件的设计与复用
景点卡片是这个平台使用频率最高的组件。首页的推荐位、景点列表页的每一行、收藏页的列表,都用这个卡片。组件接受一个spot对象作为属性,内部渲染景点图片、名称、评分、简介和标签。
这里有一个值得说的细节:图片懒加载。当列表很长时,一次性加载所有图片会导致页面卡顿。小程序里的image组件自带lazy-load属性,直接在wxml里加上就行。还有图片的裁剪模式,我统一用aspectFill,保证图片不拉伸,不变形。
另外一个我在做组件时踩过的坑是不小心把click事件绑在了组件内部的view上,导致点击卡片任意位置都能触发跳转,但点击收藏按钮时也触发了跳转。解决方案是使用catchtap代替bindtap,在事件冒泡层面阻止父级的响应。
3.3 前后端联调:那些让人抓狂的问题
联调阶段是问题最多的时候。我总结了三个典型案例,你大概率也会遇到。
第一个是域名问题。微信小程序要求所有网络请求必须是HTTPS协议,且域名要在小程序后台配置为request合法域名。开发调试阶段可以在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名”,但上线前必须换成HTTPS的正式域名。我做完前期开发后,先把Node.js服务部署到云服务器上,用Nginx配置了SSL证书,才把API地址从小程序的配置文件里替换成正式域名。这里有个容易踩的坑:如果API接口地址带端口号,比如https://api.example.com:8080,而Nginx只监听了443端口,请求会直接失败。最好的配置方式是让Nginx监听443并把请求转发到后端的3000端口,对外只有443一个入口。
第二个是参数传递格式不一致。小程序端默认的Content-Type是application/json,但如果你在wx.request里用GET方法传参数,默认会把参数拼到URL上;用POST方法就需要手动设置header里的Content-Type。我在做收藏接口时,前端有个版本漏写了Content-Type,后端拿到的是undefined,导致接口直接500。排查了半天才发现是请求头的问题。所以做小程序接口联调时,第一步就检查请求方式和请求头有没有设置对。
第三个是本地调试时的网络问题。Node.js服务跑在本地,小程序开发者工具里访问http://localhost:3000接口,在电脑浏览器里能通,开发者工具里却请求失败。原因是开发者工具的network请求走的是本机网络,local.host解析有时会出问题。你可以把接口地址改成电脑的局域网IP,比如http://192.168.1.100:3000,这样真机调试和开发者工具都能访问到。
3.4 真机调试:微信里跑起来是什么体验
在开发者工具里一切正常,不代表真机上没问题。我用iPhone和Android各做了一次真机测试,暴露了三个开发者工具里发现不了的问题。
第一是性能。开发者工具里页面加载几乎零延迟,但真机上加载几张高清景点图就开始白屏。我在首页的推荐位做了数量限制,只加载10条推荐数据,配合懒加载,白屏问题才缓解。第二是定位权限。厦门周边游平台有获取用户地理位置的功能,在开发者工具里可以模拟位置,但真机上必须弹窗请求授权。如果用户拒绝授权,需要有兜底逻辑,提示用户手动选择城市。第三是缓存和版本。小程序发的版本是有延迟的,就算你上传了新代码,用户手机上可能还是旧版本。我在小程序里接入了wx.getUpdateManager,做好版本更新的检查提示。
4. 常见问题与排查技巧实录
4.1 npm与Node.js相关问题的快速定位
项目开发过程中最频繁的环境类报错就是npm无法加载脚本,我前面已经提过PowerShell执行策略的问题。但还有另一个常见情况:npm install时某个依赖包安装失败,报错信息很长,看不懂是哪儿出的问题。我建议先看报错日志最后两行,通常会有明确的错误类型提示,是网络原因、权限原因还是依赖版本冲突。
网络原因可以通过切换npm镜像源解决。权限问题在Windows上最容易处理:以管理员身份运行CMD。依赖版本冲突则需要手动修改package.json里的版本号,锁定一个稳定版本。我在项目里把express锁定在4.19.2,mysql2锁定在3.9.7,这两个版本组合我实测过非常稳定。
4.2 微信小程序调试:paused in debugger和缓存问题
使用微信开发者工具时,会遇到一个很有迷惑性的问题:启动后页面被卡住,控制台打印paused in debugger。这不是代码报错,而是开发者工具自动进入了断点调试状态。原因是有时你在控制台里设置了断点,或者上一次调试结束后断点没有清理。解决办法很简单:在Sources面板里把断点全部移除,或者直接重启开发者工具。这个问题我在项目开发中遇到了很多次,其实是无害的,只是看着吓人。
小程序另一类问题是缓存。开发者工具里有“清除缓存”功能,真机上则要在右上角胶囊按钮里选择“重新载入”,或者删除小程序重新搜索打开。当你改了代码但真机上没看到效果,不要怀疑代码,先清缓存再看。
4.3 常见问题速查表
我把这个项目开发中遇到的问题整理成了一张速查表,方便你直接对号入座。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| npm命令执行报错,提示禁止运行脚本 | PowerShell执行策略限制 | 使用CMD执行,或用管理员PowerShell执行Set-ExecutionPolicy RemoteSigned |
| 后端接口在浏览器能通,小程序里请求失败 | 未配置request合法域名 | 开发阶段勾选不校验合法域名,上线前配置HTTPS域名 |
| wx.login获取的code传给后端,提示无效 | code被重复使用或过期 | 保证code只使用一次,后端立即换取openid |
| 真机上图片加载慢或白屏 | 图片过大或未做懒加载 | 压缩图片,给image组件加lazy-load属性 |
| 页面布局在全面屏上错位 | 未处理导航栏高度适配 | 通过wx.getWindowInfo()动态获取状态栏高度 |
| 点击卡片进入详情页,但点击收藏按钮也跳转了 | 事件冒泡未阻止 | 用catchtap替换bindtap |
这张表是我从实际开发中筛选出来的高频问题,基本覆盖了从环境搭建到真机调试的全流程。
5. 工具选型与代码组织:提升开发效率的几个习惯
5.1 版本管理与规范化提交
这个项目我使用Git做版本管理。在项目初始化时,我就把package.json、app.js等基础文件提交到了master分支,然后新建了一个dev分支开发和测试,稳定后再合并到master。这种分支管理很简单,但对于单人开发来说,能够防止改崩了没法回退的情况。
代码规范上,我用了EditorConfig统一了缩进和换行风格,ESLint做语法检查。写后端接口时,一个小小的规范是统一使用双引号、不加分号,这样前后端代码风格保持一致,看着舒服,也减少了一类隐藏的语法问题。
5.2 接口文档的重要性,别偷懒
这个项目只有我一个人开发,很多东西写代码时记得清楚,停几天再来看就有点模糊了。接口多了之后,参数传来传去很容易搞混。所以我花了一个小时把所有接口写成了Markdown文档,包括每个接口的路径、请求方法、请求参数、返回数据结构、错误码含义。写文档这件事,当时看是浪费时间,但后面调试和扩展功能时,收益巨大。
我在项目里用Apifox管理接口文档,它可以把接口调用的数据结构自动生成文档,还可以直接mock数据。我在开发小程序端的时候,后端接口还没完全写好,就先用Apifox的mock数据跑通了页面流程,等后端接口写好后,直接把基础URL切换过去,非常省时间。
5.3 用Swagger还是不用?我的选择
很多Node.js开发者会接入Swagger自动生成接口文档,但我在这个项目里没有用。原因只有一个:接口数量太少,用Swagger需要写一堆注解或装饰器,反而增加了维护成本。Apifox已经足够好用,手动维护十几个接口的文档并不累。如果你的项目接口数量超过三十个,我建议用Swagger;否则,用Apifox或直接手写Markdown文档,更轻快。
6. 项目部署与上线
6.1 Node.js服务的生产环境部署
部署Node.js服务,我用了PM2做进程管理。原因是Node.js是单线程的,如果代码里有一个未捕获的异常,整个进程就会崩溃。PM2可以在进程崩溃后自动重启,还能保存日志、配置开机自启。
具体操作分三步:第一步,在服务器上安装Node.js和PM2。第二步,把代码上传到服务器,执行npm install --production安装生产依赖。第三步,用PM2启动服务,执行pm2 start app.js --name travel-api,之后用pm2 save保存进程列表,再依次执行pm2 startup设置开机自启。
我部署的服务器配置是1核2G的云服务器,日常接口响应在50-100ms左右,扛住个人项目的流量完全没问题。
6.2 小程序上线前要做什么
小程序上线需要在小程序后台完成。审核之前要保证三个基本条件:后端域名是HTTPS且在后台配置了request合法域名;小程序类目选择正确,周边游属于旅游类目,需要提供相关资质;隐私协议和用户授权弹窗要合规,特别是涉及获取地理位置和用户信息的功能。
首次提审被驳回的概率很大,我遇到过最快的一次审核在两小时左右,慢的足足等了两天。审核驳回的原因五花八门,最让我意外的一次是“用户头像昵称获取功能需要完善隐私政策”。实际上我们并没有强制要用户授权头像昵称,但小程序的登录弹窗里展示了获取头像的说明,审核人员认为这属于收集用户信息。修改方案是:登录弹窗里明确写明用途和隐私政策链接,且允许用户拒绝授权还能正常浏览景点。改完之后再次提审,顺利通过。
7. 实操总结:这个项目做完,我最大的收获
如果你打算照着这个项目做一遍,我建议你沿途多注意这些事:一个是环境问题千万要耐心,不要一报错就怀疑代码。另一个是官方文档其实写得挺好的,遇到问题先去微信官方文档找答案。再一个是要学会记录问题和解决办法,我一边开发一边在项目根目录建了一个NOTE.md文档,里面记录了我遇到的每一个问题、报错信息、解决思路、最终方案。后来这个笔记文档的体量比接口文档还大,帮助我避免了很多重复踩坑。
这个项目本身的功能还可以继续扩展。做成“厦门周边游”只是一个场景,把景点数据换成任意城市的景点,或者加上酒店、特产、拼团功能,这套技术框架都能复用。从0到1搭一个小程序后端,掌握Node.js和微信小程序的核心开发流程,这个项目是一个很好的练手案例。
