资讯详情

UniApp旅游小程序推荐系统实战:标签画像与协同过滤落地

📅 2026/10/7 5:06:46 | 华诺云谱 👁 阅读
UniApp旅游小程序推荐系统实战:标签画像与协同过滤落地
去年接了个旅游类的小程序需求产品经理的诉求特别直白用户打开小程序能看到几个合口味的景点而不是一屏全是网红打卡地。听起来简单真正落地的时候才发现推荐这两个字后面藏着标签体系、用户画像、行为采集、冷启动一长串问题。最终项目选择了UniApp这套跨端框架来做微信小程序端推荐引擎没有上机器学习而是用了一套轻量的标签加权加协同过滤方案实测效果在中小体量数据下完全够用。这篇文章把整个项目的技术链路拆开讲一遍从技术选型、推荐算法落地、UniApp开发联调到发布上线前的配置坑尽量按实操顺序来。适合正在做小程序开发、或者准备用UniApp接旅游类推荐需求的工程师参考有数据规模不大、想快速落地推荐功能的朋友也可以直接抄作业。1. 为什么这个项目最终选了UniApp从技术选型到架构拆分1.1 需求本质表面是推荐系统底层是内容分发先把需求拆清楚。所谓旅游景点推荐系统本质上是一个内容分发问题数据库中可能有几千上万个景点POI兴趣点每个POI有坐标、门票、评分、标签、图片、简介。用户打开小程序时不可能把所有POI一次性倒给用户手机屏幕就那么大用户耐心就那么几秒必须做到千人千面把最可能符合当前用户偏好的几个景点推到首页。这里有个容易犯的错误一提到推荐就想着上协同过滤、上深度学习、上向量召回。对一个小程序项目来说数据量可能只有几千个景点、几千个日活用户跑这么重的框架纯属给自己加班。推荐系统的目标不是炫技而是让用户在有限的信息流里快速找到想去的地方。围绕这个目标我把系统拆成五个核心模块景点管理后台录入景点的名称、坐标、标签、图片、评分、开放时间等基础数据用户模块微信登录、用户画像、浏览记录、收藏、足迹推荐引擎基于标签和行为的召回排序服务输出个性化景点列表小程序端首页信息流、景点详情、地图附近、个人中心管理与运营热门景点人工干预、兜底策略配置1.2 UniApp对比原生开发的取舍点这个项目最初也纠结过要不要用微信原生小程序开发后来团队评估完还是选了UniApp。考虑因素如下表维度UniApp微信原生小程序多端复用一套Vue代码可编译到微信小程序、App、H5仅微信端要做App得另起炉灶开发效率Vue单文件组件生态丰富上手快原生WXML/WXSS写法偏自定义性能经HBuilderX编译后接近原生略有一层转换开销性能最优组件和API最底层周边生态插件市场有大量现成组件微信官方组件和API最全技能复用Vue技术栈可迁移到Web/App原生技能只能用在微信生态这个项目有一个隐含需求后续可能要做App端或者H5端如果一开始用原生小程序写将来迁移等于重写一遍。UniApp虽然性能上有一点折损但对一个以信息展示和推荐分发为核心的小程序来说性能瓶颈根本轮不到框架层真正的瓶颈在图片体积和接口设计上。1.3 数据模型设计推荐系统的地基数据模型在动手写代码之前就得定好。推荐系统最忌讳先开发页面后补数据因为画像、行为、标签这些数据如果不在源头设计好后面全得返工。我用几张核心表来支撑推荐逻辑景点表poiid、名称、城市、经度、纬度、评分、热度、封面图、标签IDs标签表tagid、标签名如亲子自然人文美食景点标签关联表poi_tagpoi_id、tag_id用户行为表user_behaviorid、用户ID、景点ID、行为类型浏览/收藏/搜索、发生时间用户画像表user_profileid、用户ID、标签权重JSON、偏好城市、活跃度用户画像表里的标签权重JSON是推荐引擎的核心输入格式大概是{自然: 0.8, 亲子: 0.6, 人文: 0.3}。这个权重不是拍脑袋定的是用户行为通过积分规则累积出来的后面会细说。1.4 技术栈全景最终的技术栈如下前端框架UniAppVue 3语法编译目标为微信小程序后端服务Spring Boot MyBatis Plus部署在云服务器上数据库MySQL 8.0存放景点、标签、用户、行为数据缓存Redis用来缓存推荐结果和热点数据推荐计算Java后端定时任务 轻量算法输入用户ID输出有序景点ID列表小程序端不需要承担推荐计算任务只负责展示结果。这个分离很重要小程序端跑复杂计算既耗性能又费流量而且推荐逻辑更新时还必须发版放在服务端随时可调。2. 推荐引擎不堆算法标签画像、召回排序与冷启动的落地实现2.1 景点标签和用户画像推荐的地基推荐系统的地基不是算法而是标签。一个景点如果没有任何标签推荐引擎就算把数学玩出花来也算不出它适合谁。我先给景点规划了基础标签体系按旅游决策场景分成几个维度主题类型自然风光、人文古迹、主题乐园、城市漫步、宗教寺庙、科普研学人群偏好亲子、情侣、老人、朋友聚会、独自旅行场景属性网红打卡、小众秘境、摄影胜地、夜游、徒步登山、水上项目每个景点建议打3到5个标签宁缺毋滥。比如西湖可以打自然风光城市漫步网红打卡情侣但不要打主题乐园这种明显不符合的标签。标签不准后面画像再准也白搭。用户画像的构建思路是行为映射到标签用户浏览了一个景点就把这个景点的标签权重累加到用户的标签权重上再乘时间衰减因子。2.2 召回与排序一条公式把推荐跑起来推荐的完整流程分召回和排序两步。召回阶段根据用户的画像标签从景点库里捞出一批候选景点。简单做法是遍历所有景点计算每个景点标签集合与用户标签权重的匹配度取TopN作为候选集。这里我用余弦相似度计算用户画像和景点标签之间的匹配分matchScore (用户标签权重向量 · 景点标签向量) / (用户画像权重模长 × 景点标签模长)这个值在0到1之间越接近1说明这个景点越符合用户的兴趣偏好。排序阶段在召回集的基础上加距离、热度、评分做加权。最终评分公式是finalScore 0.4 × 标签匹配度 0.3 × 距离因子 0.2 × 热度分 0.1 × 评分分其中距离因子按线性衰减1 - min(实际距离/搜索半径, 1)也就是说距离越近得分越高。热度分用景点的访问量归一化得到评分分用景点的用户评分除以5得到。权重可以根据运营需要随时调整比如旅游旺季可以把距离因子调高引导用户去附近景区而非大老远跑到别的城市。这部分在Java里就是一次简单的遍历计算几千个景点性能完全没问题。核心代码如下public ListPoi recommend(Integer userId, Double lat, Double lng, int topN) { // 1. 读取用户画像标签权重 UserProfile profile userProfileMapper.selectByUserId(userId); // 2. 召回遍历景点计算标签匹配度 ListPoi allPois poiMapper.selectAll(); ListScoredPoi scoredList new ArrayList(); for (Poi poi : allPois) { double tagScore calcTagMatch(profile.getTagWeights(), poi.getTags()); double distance DistanceUtil.distance(lat, lng, poi.getLat(), poi.getLng()); double distanceFactor Math.max(0, 1 - distance / searchRadius); double hotScore poi.getHotRank() / maxHotRank; double ratingScore poi.getRating() / 5.0; double finalScore 0.4 * tagScore 0.3 * distanceFactor 0.2 * hotScore 0.1 * ratingScore; scoredList.add(new ScoredPoi(poi, finalScore)); } // 3. 排序取TopN scoredList.sort((a, b) - Double.compare(b.getScore(), a.getScore())); return scoredList.subList(0, Math.min(topN, scoredList.size())) .stream().map(ScoredPoi::getPoi).collect(Collectors.toList()); }这里还有一个协同过滤的轻量落地。当用户的行为数据积累到一定量后可以基于看了A景点的人也看了B景点做关联推荐。实现不复杂在用户行为表里统计共现次数建立景点间的共现矩阵存入Redis当推荐结果里标签匹配度都太低时用协同过滤结果做补充。2.3 冷启动方案新用户不看空页面冷启动是推荐系统绕不开的问题。新用户没有任何行为数据画像为空如果直接跑推荐公式所有景点的标签匹配度都是0最终只能按距离、热度、评分排序展示效果跟随便看看没有区别这就失去了推荐的意义。我的冷启动方案分三层第一层根据用户当前定位推荐附近热门景点Top10用城市优先兜底第二层如果用户授权了手机号尝试根据微信登录返回的性别年龄信息如果有做粗略画像匹配第三层在首页推荐流里混入几类热门标签的代表性景点每个标签出1到2个既保证多样性也给用户提供点开即训练画像的素材冷启动阶段最忌讳的是推荐结果过于单一。比如用户在大理就给他推10个洱海周边景点结果用户觉得全是海景房。我的做法是强制标签多样候选集按标签分桶每个桶取Top2然后拼接成最终的推荐流。2.4 为什么不在小程序端直接算推荐这个项目从一开始就定了一个原则小程序端坚决不做推荐计算。原因有三。第一数据安全问题。推荐逻辑放在前端等于把整个推荐策略暴露给用户运营和商业价值全没了。考察一下竞品没有哪个成熟产品会在客户端直接跑核心算法。第二性能问题。小程序端JavaScript引擎处理几千条数据的遍历计算虽然不至于卡死但在低端安卓机上用户滑个列表还要等本地算完才有数据体验很差。第三更新问题。推荐策略必须能快速迭代。周末景区人流量异常运营想临时调权重如果逻辑在前端就得提审、发版等两三天审核黄花菜都凉了。放在服务端Redis配置一改秒级生效。小程序端只做一件事调用后端接口拿推荐结果再渲染页面。这样职责清晰也方便后续推荐逻辑升级。3. 从HBuilderX创建工程到微信开发者工具跑通开发链路全记录3.1 HBuilderX创建项目与工程结构UniApp开发我用的HBuilderX它是DCloud官方的IDE对UniApp的支持最无缝创建、编译、调试、打包一站式解决。用命令行的方式也可以但HBuilderX对新手更友好不用自己配一堆环境变量。创建项目的流程打开HBuilderX → 文件 → 新建 → 项目 → 选择uni-app模板 → 项目名称填travel-recommend-miniapp框架选Vue 3。HBuilderX会自动生成一套标准的UniApp目录结构。创建完的目录如下每个目录职责要明确travel-recommend-miniapp/ ├── pages/ # 页面目录所有页面都放这 │ ├── index/ # 首页推荐Feed │ ├── explore/ # 附近景点/地图页 │ ├── detail/ # 景点详情页 │ └── mine/ # 个人中心 ├── components/ # 可复用组件景点卡片、标签等 ├── api/ # 接口请求封装 ├── static/ # 静态资源 ├── utils/ # 工具函数距离计算、格式化等 ├── App.vue # 应用入口生命周期全局样式 ├── main.js # Vue实例入口 ├── manifest.json # 应用配置包含小程序AppID等 ├── pages.json # 页面路由和导航栏配置 └── uni.scss # 全局样式变量pages.json相当于小程序原生里的app.json页面路由、TabBar、导航栏样式全在这里配置。这个文件我建议一开始就完整配置好别等页面多了再补后面路由跳转全依赖它。3.2 manifest.json和AppID配置最容易被忽略的第一步这是整个开发流程里最容易踩坑的地方。manifest.json里有个微信小程序配置区块里面的appid一定要填真实的小程序AppID否则微信开发者工具编译后照样会报错或功能受限。获取AppID的流程微信公众平台注册小程序账号登录后在开发 开发管理 开发设置里可以找到AppID。这里分两种测试号AppID和正式AppID。开发阶段可以用测试号但调试登录、获取手机号等能力时测试号有限制建议尽早用正式号。配置路径manifest.json → 微信小程序配置 → 填入AppID。填完之后还要在HBuilderX里设置微信开发者工具运行路径。在HBuilderX的运行 运行到小程序模拟器 微信开发者工具里第一次会要求指定微信开发者工具的安装路径。这里注意微信开发者工具需要开启服务端口才能接收HBuilderX的推送编译。微信开发者工具设置路径右上角设置 → 安全设置 → 服务端口 → 打开。不开这个端口HBuilderX编译完根本推不进去。3.3 微信开发者工具联调从编译到真机预览配置完成后HBuilderX工具栏点运行 运行到小程序模拟器 微信开发者工具编译完会自动打开微信开发者工具加载项目。这里的联调流程我建议养成固定的习惯先看微信开发者工具的Console面板有红色报错优先处理网络请求在Network面板看开发阶段记得勾选不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书真机预览用微信扫一扫手机上装的是体验版注意开发者工具上预览按钮生成的二维码有时效开发阶段总会遇到一个窒息场景真机上样式和模拟器里完全不一样。模拟器用的是Chromium内核渲染真机是XWeb内核部分CSS属性支持度有差异。遇到这种问题我的做法是尽量用Flex布局少用绝对定位圆角和阴影适当保守一点。这个项目首页Feed卡片曾经在模拟器里好好的真机上出现底部留白问题排查发现是padding-bottom和env(safe-area-inset-bottom)冲突导致的后面统一用安全区适配才解决。3.4 菜单路由、TabBar与页面间通信TabBar配置在pages.json里我配了三个首页推荐、附近探索、我的。图标用的是iconfont字体图标放在static目录。这里有个小技巧TabBar的图标尺寸建议遵循微信官方规范81px × 81px图标过大或过小都会影响真机显示。页面跳转要注意UniApp里uni.navigateTo和uni.switchTab是有区别的。navigateTo用于页面栈内的普通页面跳转switchTab用于跳转到TabBar页面两者不能混用。从景点详情页返回首页时如果用了navigateBack页面栈里可能没有上一页会直接卡死。我的做法是详情页入口统一用navigateTo详情页里放一个返回首页按钮用uni.reLaunch直接重置页面栈到首页。页面间传参我用三元运算符式的URL传参简单直接。比如详情页跳转uni.navigateTo({ url: /pages/detail/detail?id${poiId} });接收参数时用onLoad(options)里的options.id。复杂对象传参用eventChannel或者全局状态管理但项目里我尽量少用全局Store因为小程序页面刷新后全局数据容易丢还是要以URL参数和本地缓存为主。4. 景点Feed、地图定位与附近推荐三个核心页面的实现细节4.1 首页推荐Feed组件拆分与骨架屏首页是整个小程序的门面推荐流长什么样直接决定用户愿不愿意留。把首页拆成三个区块顶部城市定位 标签偏好快捷选择、推荐景点卡片Feed、底部加载更多。推荐卡片我抽成了一个组件PoiCard在components目录下。这个组件接收景点对象渲染封面图、名称、标签、评分、距离信息。组件化有两个好处详情页关联推荐、探索页列表都要用同一套卡片样式改样式时只改一处。卡片布局用的是经典的上下结构上面是大图下面叠加文字信息。封面图这块有个性能细节图片尺寸一定要压缩。微信小程序单包限制2MB图片资源过大不只影响体积还会在列表滚动时产生白屏和卡顿。我的做法是后端接口直接返回裁剪后的图片URL统一缩放到750px宽度使用WebP格式实测体积能小60%以上。骨架屏是提升首屏体验的关键。推荐接口返回前页面不能白着用骨架屏占位给用户马上就好的暗示。UniApp里实现骨架屏很简单在页面loading状态渲染几个灰色块模拟卡片布局。重点骨架屏样式要跟真实卡片保持一致否则用户会感觉页面跳了一下。接口请求封装我放在api目录下代码如下// api/index.js const BASE_URL https://api.example.com; export function getRecommendList(data) { return new Promise((resolve, reject) { uni.request({ url: ${BASE_URL}/api/recommend, method: GET, data, success: (res) resolve(res.data), fail: (err) reject(err) }); }); }这里注意不要每次请求都写一遍uni.request封装成Promise后页面里用async/await调用代码会清爽很多。Loading状态我统一用页面级变量控制不用uni.showLoading因为showLoading在快速频繁请求时会出现闪烁体验很糟。4.2 地图组件与景点标记探索页的核心探索页的地图用的是UniApp内置的map组件它底层映射微信小程序的map组件。实现逻辑不复杂拿到用户当前定位坐标然后查询附近的景点POI在map上渲染marker标记点。map组件的核心配置map :latitudelat :longitudelng :markersmarkers :scale12 show-location markertaponMarkerTap /mapmarker数据要按微信的要求格式组织id、latitude、longitude、iconPath、width、height。iconPath可以是自定义的景点类别图标比如自然景点用一个绿色树图标人文景点用一个红色建筑图标这样地图一眼看过去就很有信息层。markertap事件返回选中的marker的id根据id去查询景点详情再跳转。这块有个容易忽略的点map组件在页面中占位很大如果地图页和列表页共用同一个页面建议用tab切换而不是两个页面互跳这样地图状态和滚动位置都能保持。4.3 定位授权与距离计算家政服务式的本地化推荐距离计算是旅游推荐绕不开的能力。获取定位有两条路uni.getLocation获取经纬度微信原生wx.chooseLocation让用户在地图上手动选点我的场景是首页自动定位城市、探索页获取附近景点所以用uni.getLocation就够了。uni.getLocation({ type: gcj02, isHighAccuracy: true, success: (res) { this.lat res.latitude; this.lng res.longitude; // 调用推荐接口传入坐标 this.fetchRecommend(); }, fail: () { // 用户拒绝授权用默认城市兜底 uni.showToast({ title: 定位失败显示默认推荐, icon: none }); } });坐标类型用gcj02这是国测局坐标标准微信小程序内map组件和接口返回的景点坐标都用这个不要误用wgs84否则地图上标记位置会偏几百米。拿到坐标之后距离计算是后端做的前端只负责展示。两点距离用Haversine公式算这个公式就几行代码Java里用Math库就能写public static double distance(double lat1, double lng1, double lat2, double lng2) { double radLat1 Math.toRadians(lat1); double radLat2 Math.toRadians(lat2); double a radLat1 - radLat2; double b Math.toRadians(lng1) - Math.toRadians(lng2); double s 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) )); return s * 6371.0; // 地球半径6371公里 }注意不要在JS前端算距离。一是后端算可以按距离参与推荐排序二是前端JS的浮点精度偶尔会出幺蛾子显示距离多出几十米会被用户吐槽。4.4 图片资源优化与分包策略这个项目踩过一个大坑开发阶段图片全放在static目录里用了很多体积很大的JPG。结果微信开发者工具编译时报主包体积超过2MB限制。后来我把策略改成所有景点图片全部走CDN按需加载不进小程序包只有TabBar图标、默认占位图等必须的本资源才放在static图片URL由后端接口下发给前端前端直接用image标签的:src属性渲染对于实在需要打进包里的资源用image组件的懒加载属性lazy-load滚动列表时图片进入视口才开始加载性能提升明显。如果页面数量多还可以用微信小程序的分包加载能力把景点详情页等低频页面拆到分包里主包只放TabBar三个主页面这样主包体积能控制在2MB以内。UniApp里在pages.json中配置subPackages字段即可。5. 登录鉴权和行为埋点让推荐系统越用越准的数据闭环5.1 uni.login静默登录与用户身份绑定推荐系统要形成数据闭环前提是知道谁在操作。微信小程序登录用的是uni.login它的核心是拿到一个code然后后端拿着code去微信服务器换openid和session_key。uni.login({ provider: weixin, success: async (loginRes) { const res await api.login({ code: loginRes.code }); uni.setStorageSync(token, res.data.token); } });后端的处理逻辑是接收前端传上来的code调用微信的https://api.weixin.qq.com/sns/jscode2session接口拿到openid。openid是用户的唯一标识首次登录则创建用户记录已存在则直接返回token。整个登录过程对用户是无感的不用弹窗不用授权所以叫静默登录。用户第一次打开小程序时如果用户没有任何明确动作只要触发了uni.login后端就已经把这个用户识别出来了。这里的关键点用户画像和推荐结果都可以先产生等用户授权手机号后再把手机号绑定到已有用户记录上而不是等手机号授权了才建立用户账户。5.2 手机号授权的正确打开方式获取手机号这个功能是微信小程序商业化的常见需求比如做会员系统、优惠券触达。它需要用到button组件的open-typegetPhoneNumber然后前端拿到code传给后端换手机号。代码示例button open-typegetPhoneNumber getphonenumberonGetPhoneNumber授权手机号/buttononGetPhoneNumber(e) { if (e.detail.errMsg getPhoneNumber:ok) { // 将code发送给后端 api.bindPhone({ code: e.detail.code }) .then(res { uni.showToast({ title: 绑定成功, icon: success }); }); } else { // 用户拒绝授权不要一直弹窗 uni.showToast({ title: 未授权手机号, icon: none }); } }后端拿这个code调用微信接口换手机号注意必须传openid因为手机号换取的接口要求用户必须已经登录否则返回错误码。这是产品设计上的一个坑不要一进小程序就强制用户授权手机号。微信对这种强制授权的审核是零容忍的而且用户会有强烈的被冒犯感。我的做法是用户第一次点击领取优惠券或查看完整行程这类业务需要时才弹出手机号授权并且允许用户拒绝。拒绝后仍然可以正常浏览景点只是部分需要手机号的功能暂时不可用。5.3 行为埋点浏览、收藏、搜索的数据采集有了用户身份下一步是采集行为。推荐系统的AI含量全靠行为数据的质量埋点设计直接影响模型效果。我埋了四类行为分别对应不同场景行为类型触发时机权重浏览进入景点详情页1收藏点击收藏按钮3搜索搜索关键词2停留时长页面停留超过10秒1.5埋点上报用uni.request异步发送不阻塞用户操作。为了防丢我在本地的storage里累积了上报事件然后定时批量上传。这里注意埋点接口和业务接口最好分开不要互相阻塞否则埋点报错会影响正常页面加载。采集到的行为数据进入user_behavior表后后端定时任务会定期更新用户画像。有个容易被忽略的细节行为数据只有用户处于登录状态才有意义。如果在用户未登录时就产生行为这个行为数据要么不收集要么等用户登录后再挂载到用户ID上。我是用本地缓存的临时ID来标记未登录用户登录后把临时ID关联到正式用户ID避免丢数据。5.4 画像更新与推荐结果刷新机制用户画像更新不能每次行为都实时全量计算那样数据库压力很大。我设计了三个更新粒度实时更新用户点击收藏按钮时同步更新画像中的标签权重让用户立刻感知到推荐变化定时更新每天凌晨跑一次批处理根据当天全部行为重新计算画像手动触发用户下拉刷新推荐列表时强制后端做一次增量计算推荐结果的缓存策略是推荐结果按用户ID缓存10分钟。用户在10分钟内重复进入首页直接返回缓存不用每次重新计算。但如果用户产生了新的行为则主动清除该用户的推荐缓存让下一次请求重新计算。这样既能保证实时性又不至于频繁计算。最关键的还是反馈闭环用户往下滑了很久点进去看的永远是同一种类型的景点那说明画像更新可能太滞后了。我会运营同学配合做一个小功能在推荐流里插入不喜欢按钮用户点掉某个景点后这个景点的标签权重在当前会话内降低50%系统会减少同类景点的推荐。这个功能虽然偶尔被吐槽点不干净但在画像冷启动阶段非常有用。6. 发布上线前的配置清单与真机踩坑记录6.1 request合法域名上线卡脖子的第一道坎开发阶段可以在微信开发者工具里勾选不校验合法域名上线后这个选项就失效了。微信小程序要求所有uni.request请求的域名必须在微信公众平台后台配置为request合法域名并且必须是HTTPS。配置路径微信公众平台 → 开发管理 → 开发设置 → 服务器域名 → request合法域名。坑点集中在三个方面。第一域名必须备案而且不能是IP地址。很多团队开发时用http://192.168.1.100:8080开发阶段没问题上线前必须换成已备案的HTTPS域名。第二域名配置生效有延迟修改后建议等5到10分钟再测试不要一改完就狂请求然后怀疑人生。第三正式环境不要用uni.request直接访问IP地址或内网域名微信审核时会直接驳回。遇到过同行为了快速上线把后端接口放在阿里云函数计算上结果域名没备案小程序审核被连续驳回两次最后只能临时买了个备案域名才解决。我的建议是项目建好后第一周就去申请备案域名并配置HTTPS证书开发阶段就开始用正式域名做联调不要等到上线前再来折腾。6.2 主包2MB限制图片和代码怎么瘦身微信小程序主包限制2MB这个数字卡死了无数项目。UniApp项目编译后JavaScript、WXML、WXSS、静态资源都会计入体积。我的瘦身步骤第一步把图片全部移到CDN远程图片不计入主包体积第二步不用的组件和库坚决删掉。比如开发阶段引了moment.js做时间处理一个库就300KB后来换成自己写的几行格式化函数省下来的空间非常可观第三步低频页面拆到分包详情页、搜索页、我的足迹都放subPackages里第四步压缩静态资源字体图标能用SVG就不要用多张PNG编译后如果提示source size exceeds max limit看HBuilderX的编译日志可以知道具体是哪个文件占了大头。我有一个临时大招如果实在超了可以把整个页面用大图懒加载把一些组件改成按需注入但这个只能应急根治还是在资源管理。6.3 顶部导航栏和胶囊按钮的高度适配微信小程序的导航栏很特殊不同机型上胶囊按钮右上角的...和○高度不同刘海屏和非刘海屏的状态栏高度也不同。如果页面里有自定义导航栏navigationStyle: custom就得自己适配高度。获取正确高度的方法const systemInfo uni.getSystemInfoSync(); const statusBarHeight systemInfo.statusBarHeight; // 状态栏高度 const menuButton uni.getMenuButtonBoundingClientRect(); // 胶囊按钮位置信息 const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height;这个计算结果就是自定义导航栏的总高度页面顶部留出statusBarHeight navBarHeight的空间导航栏内容标题、返回按钮就不会被胶囊按钮挡住。坑点提醒uni.getMenuButtonBoundingClientRect()在部分安卓机型上返回值可能不准确建议在onReady之后调用并且处理异常情况设置一个默认值比如45px。另外iPhone 14 Pro等带灵动岛的机型状态栏高度更高不要写死必须动态计算。6.4 体验版发布、审核注意事项与灰度策略开发完进入发布流程时有几个容易忽略的细节。体验版不是正式版。微信开发者工具点上传按钮填好版本号和备注然后到微信公众平台版本管理里把该版本设为体验版。体验版二维码发给测试人员测试人员必须成为小程序的体验成员才能打开。这里注意体验版和正式版的域名校验都是强制生效的不能因为正在测试就跳过HTTPS。审核环节有几个硬性要求需要提前自查小程序首页必须有实质内容不能是一张海报或一个登录框涉及旅游信息展示最好在页面底部放上免责声明比如景点信息仅供参考请以景区实际开放情况为准用户隐私协议必须明确说明收集了位置信息和行为数据微信审核对隐私协议越来越严格操作按钮不能诱导分享比如分享给好友解锁更多景点这类逻辑会被判违规灰度策略上我习惯在上线初期用白名单灰度后端配置一个灰度开关只让内部测试账号走新版推荐算法其他用户走旧版本兜底。等观察一段时间数据表现稳定再逐步放量到100%。这样做最大的好处是推荐逻辑有问题时不会直接暴露到所有用户面前回滚也快。最后说一句个人体会做这个项目最大的感悟是推荐系统在小程序场景下算法权重远不如数据干净度重要。标签打准、行为埋点不漏、用户身份尽早统一这三件事做好了哪怕只用简单的公式排序效果也能吊打那些数据一团糟却硬上深度学习的项目。如果你正准备做类似的需求我的建议是先把数据模型和埋点设计清楚再谈推荐策略顺序反了后面会一直在填坑。另外上线前一定要用自己的真机完整走一遍从启动到授权的流程模拟器里跑得通不代表真机上不会出问题。这套方案在中小体量的旅游推荐场景下我已经跑过一遍你可以放心照着这个思路去搭。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑