尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序云开发实战:个人事务物品备忘录系统设计

微信小程序云开发实战:个人事务物品备忘录系统设计 你手机里现在躺了几个备忘录我数过自己手机待办应用两个、笔记应用三个、相册里还存着几十张“这玩意儿当时放在这儿”的照片。问题是事务提醒和物品位置记录互相割裂真到找东西或者赶截止时间的时候信息散得根本翻不出来。所以我用微信小程序给自己做了一套“个人事务物品存储备忘录系统”把事务管理、物品存放位置和备忘录归档收进统一的数据模型用完整的系统设计方法从需求、架构到实现一步步落地。这篇内容既是一份微信小程序的实战开发记录也包含我在方案取舍和踩坑过程中的真实经验适合正在做相关课设、毕设的同学也适合想把个人数据整理清楚的小程序开发者。1. 项目定位与需求边界为什么要把三类数据塞进同一个小程序1.1 用户真正的痛点不是缺工具而是信息割裂我在做这个项目之前手机上同时装着三四个效率类App待办事项用一个笔记用一个记录“东西放在哪里”靠的是备忘录加相册截图。看起来每个工具都很专注但真正找东西的时候你得先回忆当时把这条信息记在了哪个App里再打开去搜索。如果这条信息压根没记录那就是一场翻箱倒柜。真正让我下决心做这个小程序的是去年换季整理衣物。我把十几箱衣物分门别类收进柜子随手在备忘录里记了几条“冬被在卧室衣柜顶”“换季鞋在阳台储物柜”。结果一个月后再找一件外套我在备忘录里翻来翻去愣是没找到当时的记录因为那天我是拍了一张柜子照片存进了相册。这个场景特别典型事务、物品、笔记本质上是同一个人在不同场景下的碎片化信息它们彼此关联却被我硬生生拆散在不同App里。所以我给这个系统定的核心定位是私人的、高频录入、即时要查的个人信息管理工具。事务的关键索引维度是时间物品的关键索引维度是位置备忘录的关键索引维度是主题。当这三类数据进入同一个数据库之后很多以前没法做的联动就自然出现了比如找某件物品的时候顺便能看到这个位置附近还有哪些东西搜一个关键词既能搜到事务、也能搜到笔记、还能搜到关联物品。1.2 功能边界MVP阶段哪些必须做、哪些坚决不做做系统设计最容易翻车的地方不是功能太少而是功能太多忍不住想加。我第一次架构设计的时候甚至想过做“家庭共享空间”“物品心情打卡”“事务完成率周报”这类功能后来全部砍掉了。我的判断标准很简单这个功能能不能在两天内实现并把体验闭环如果答案模糊就不要进入MVP。最终确定的MVP功能边界如下模块必须实现明确不做事务管理创建/编辑/删除、优先级、状态流转、时间设置、提醒不做子任务拆分、不做团队协作物品存储物品增删改查、数量、位置绑定、照片不做条码扫描、不做库存盘点报表位置管理树形层级位置、位置路径展示不做地图定位、不做室内导航备忘录文本记录、标签、置顶不做富文本编辑器、不做云同步到笔记类App全局搜索关键词跨三表检索不做语义搜索、不做语音搜索基础功能登录、数据隔离、缓存、分页不做多端同步、不做Web管理后台这个边界定下来之后整个系统的开发量就明确了很多。后来也确实证明这个“克制”是项目能快速跑起来的关键。你如果也在做类似项目我建议先画一张这样的功能边界表把“想做”和“必须做”分开这比任何技术选型都重要。1.3 为什么是微信小程序而不是原生App这个问题我在需求阶段反复问过自己。最开始我想过做一个iOS原生App因为个人数据管理工具用原生App体验确实更好。但考虑到开发成本、上架审核、以及我自己又不能完整维护两端最终选微信小程序有四个现实原因。一是免安装。小程序扫码即用对个人工具来说入口成本低意味着我打开它的频率会高很多。二是云开发能力。微信小程序云开发自带数据库、云函数、云存储和定时触发器一个项目从0到1不需要自己买服务器、配域名、办备案对个人开发者和小型课设项目非常友好。三是订阅消息可以做事务提醒这个后面我会详细说它比本地通知更适合小程序的运行模型。四是数据天然隔离。每个用户有独立的openid云开发数据库自动注入_openid字段做用户维度数据隔离非常方便。当然也有代价小程序包体积有限制、审核周期比个人开发可控性差、订阅消息还有“一次性授权”的奇怪规则。这些限制在后面的踩坑部分会逐个展开。2. 技术选型与整体架构设计2.1 前端框架原生微信小程序还是uni-app这个项目在技术选型阶段一个绕不开的话题就是跨端框架。周围不少同事推荐直接用uni-app或者Taro理由是以后可以一键发布到H5甚至App。我把两个框架都试了一下最后还是选了原生微信小程序。对比项原生小程序uni-appTaro多端发布仅微信微信、H5、App等多端微信、H5、React Native等多端学习成本低官方文档直接查中要理解Vue语法和编译差异中需要React基础调试体验最直接微信开发者工具完整需要依赖HBuilderX和微信工具联动需要依赖Node工具链云开发支持原生API最顺畅支持但部分云能力API要条件编译支持但封装层较厚包体积可控框架运行时约几百KB框架运行时约几百KB我这个系统只在微信内使用没有跨端诉求原生API和云开发的配合最直接所以原生是合适的选择。但如果你有“毕业设计要体现多种技术栈”或者“以后要同步出一个H5版本”的需求uni-app完全可以用。我做这套设计时特意把数据模型、云函数接口设计成与前端框架无关这样万一以后要从原生迁移到uni-app前端页面需要改云端逻辑和后端数据结构都不需要动。2.2 后端与数据存储小程序云开发的取舍个人项目最怕的是“忙于学运维没时间做功能”。我在做这个系统时认真对比过三种后端方案自建服务器、小程序云开发、第三方BaaS后端云。方案优点缺点适合场景自建服务器 Spring Boot/Node.js技术可控性强不受平台限制毕业设计更完整需要域名备案、HTTPS证书、服务器费用、运维课设或毕设要求传统前后端分层微信小程序云开发免运维、云数据库/云函数/云存储一体、与小程序无缝集成平台绑定无法脱离微信生态个人工具、MVP验证、快速上线第三方BaaS如LeanCloud、Bmob有免费额度、跨端免费额度有限、商用要收费、数据安全不好把控验证产品想法我最终选了云开发核心原因是这个系统是纯个人使用我不需要一个传统后台管理界面也不需要关心并发和备份。云开发的云数据库本身就是文档型数据库跟MySQL这类关系型数据库相比在存“用户自定义字段多、结构不固定”的个人信息场景下反而更灵活。需要说明的是如果你做的课设明确要求“必须用SSM框架”“必须用MySQL”那自建后端也完全可以。我的数据模型在设计时就是按照“一张表对应一个数据库集合”的思维做的todos对应事务表、items对应物品表前端接口也封装成统一返回结构将来把云函数替换成Java接口前端只需要改base URL和返回字段解析整体成本不高。2.3 数据模型设计四个核心集合的字段规划数据模型是整个系统设计里最关键的部分比写页面费的时间还多。我不建议上来就建表而是先把事务、物品、备忘录、位置四类信息的“属性清单”列出来再转成字段。四个核心集合如下todos事务集合字段类型说明_idString记录ID_openidString用户标识云开发自动注入titleString事务标题contentString备注详情priorityNumber优先级从1到3statusString状态pending/in_progress/done/cancelleddueTimeDate截止时间remindTimeDate提醒时间repeatTypeString重复类型none/daily/weekly/monthlycategoryString分类标签createTimeDate创建时间updateTimeDate更新时间items物品集合字段类型说明_idString记录ID_openidString用户标识nameString物品名称locationIdString所在位置的IDlocationPathString冗余的位置完整路径如“书房右侧书柜第三层”categoryString物品分类quantityNumber数量unitString单位如个/箱/袋photoString云存储图片fileIDnoteString补充备注expiryDateDate过期时间可选createTimeDate创建时间updateTimeDate更新时间locations位置集合字段类型说明_idString位置ID_openidString用户标识nameString位置名称parentIdString父级位置ID顶级为空fullPathString冗余完整路径orderIndexNumber同级排序notes备忘录集合字段类型说明_idString记录ID_openidString用户标识titleString笔记标题contentString笔记正文tagsArray标签数组pinnedBoolean是否置顶createTimeDate创建时间updateTimeDate更新时间这里最值得解释的一个设计决策是locationPath冗余字段。理论上通过parentId递归查祖先节点就能拼出完整路径但每次渲染物品列表都要持数据库查询体验很差代码也很啰嗦。我用空间换时间在保存位置时直接把完整路径拼好存进去列表页一次查询就能直接展示。冗余设计在关系型数据库里叫“反范式”在云开发这种文档数据库里是最常用的实践目的就一个读多写少的场景尽量让查询不JOIN、不递归、一次拿完。另一个重要的设计点是repeatType。事务的重复类型我并没有做复杂的“每隔N天”的表达式而是只支持无/每天/每周/每月四种枚举因为对个人事务来说真正高频的需求就是周期性固定事项。复杂表达式会极大增加提醒逻辑的复杂度MVP阶段没必要。2.4 项目目录结构与代码分层系统目录我按典型的小程序云开发结构组织每个模块的页面、组件、云函数分开避免所有代码堆在app.js里。miniprogram/ ├── app.js ├── app.json ├── app.wxss ├── pages/ │ ├── todo/ │ │ ├── index.js │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── edit.js │ ├── item/ │ │ ├── index.js │ │ ├── index.wxml │ │ ├── index.wxss │ │ ├── edit.js │ │ └── location.js │ ├── note/ │ │ ├── index.js │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── edit.js │ ├── search/ │ │ ├── index.js │ │ ├── index.wxml │ │ └── index.wxss │ ├── mine/ │ │ ├── index.js │ │ └── index.wxml ├── components/ │ ├── empty-view/ │ ├── card-item/ │ └── nav-bar/ ├── utils/ │ ├── date.js │ ├── storage.js │ └── request.js ├── cloudfunctions/ │ ├── sendRemind/ │ ├── uploadImage/ │ └── getLocationTree/ └── images/页面上事务、物品、备忘录各一套增删改查页面搜索和“我的”作为两个聚合入口。云函数按职责拆分提醒、图片上传、位置树查询这些独立能力都单独放函数。这个结构可能看起来朴素但胜在清晰第三方开发者接手也能很快找到对应模块。3. 核心功能模块的落地实现3.1 事务管理模块状态流与提醒链路事务模块是全系统最先做的因为它的业务逻辑最丰富、也最能体现“系统设计”的完整度。事务的状态我定义了四个待开始、进行中、已完成、已取消。新建事务默认是待开始用户点击“开始处理”后变成进行中勾选完成变成已完成不需要的可以取消。列表页则按状态分组展示进行中的排最前待开始的按截止时间排序已完成和已取消折叠成历史区避免页面被历史事务塞满。创建和编辑页面里比较核心的表单字段有标题、备注、优先级、分类、截止时间、提醒时间、重复类型。其中优先级用三个级别高/中/低列表里用不同颜色标签显示。我曾纠结过要不要做“自定义标签”后面发现事务分类用固定几个预设值就够了最终只内置了“工作/生活/学习/其他”四类。这件事告诉我很多时候“配置化”诱惑会害死人能枚举的绝不开放自定义项目复杂度会小很多。提醒功能是整个事务模块里最复杂的部分。最初我天真地想在前端用一个setTimeout到点提醒后来发现小程序在后台运行时定时器会被系统冻结切后台就失效。真正的方案是云函数定时触发器 订阅消息。实现思路分三步第一步用户创建或修改事务时如果设置了提醒时间就调一次订阅消息授权接口让用户确认允许接收提醒通知wx.requestSubscribeMessage({ tmplIds: [模板ID], success(res) { // res[模板ID] accept 表示接受 if (res[模板ID] accept) { // 本地记录订阅剩余次数加1 wx.setStorageSync(subCount, (wx.getStorageSync(subCount) || 0) 1); } } });第二步在云函数里配置定时触发器比如每隔两分钟扫描一次数据库找出所有当前时间点落在remindTime前后几分钟内、且还没发过提醒的事务记录// cloudfunctions/sendRemind/index.js const cloud require(wx-server-sdk); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db cloud.database(); const _ db.command; exports.main async () { const now Date.now(); const start new Date(now - 2 * 60 * 1000); const end new Date(now 2 * 60 * 1000); const res await db.collection(todos) .where({ remindTime: _.gte(start).and(_.lte(end)), status: _.in([pending, in_progress]), remindSent: _.neq(true) }) .get(); for (const doc of res.data) { await cloud.openapi.subscribeMessage.send({ touser: doc._openid, templateId: 模板ID, page: pages/todo/index, data: { thing1: { value: doc.title }, time2: { value: doc.remindTime.toLocaleString() } } }); await db.collection(todos).doc(doc._id).update({ data: { remindSent: true } }); } };第三步在cloudfunctions/sendRemind/config.json里配置定时触发器{ permissions: { openapi: [subscribeMessage.send] }, triggers: [ { name: remindTimer, type: timer, config: 0 */2 * * * * * } ] }这个方案的要点是把提醒的“判断”从客户端拿到了服务端小程序哪怕被用户杀掉云函数照样能按点扫描数据库并通过订阅消息触达用户。这也是我做这个项目收获最大的一次认知升级在受限端的系统设计里能放到服务端的逻辑一定不要留在端上。3.2 物品存储模块位置树与快速定位物品模块的设计核心是“位置树”。一开始我做过扁平的“位置下拉框”就是列一排“客厅、卧室、厨房、书房”让用户选。但真实场景根本不是这个粒度你需要的是“书房 右侧书柜 第三层”这种层级。所以我改成了树形位置模型。位置树用locations集合维护每个位置记录自己的parentId和fullPath。添加位置的时候用户先选父级位置也可以不选作为顶级位置再填当前节点名称保存时自动拼接完整路径async function addLocation(name, parentId) { let fullPath name; if (parentId) { const parent await getLocationById(parentId); fullPath ${parent.fullPath}${name}; } await db.collection(locations).add({ data: { name, parentId: parentId || , fullPath, orderIndex: 0, createTime: db.serverDate(), updateTime: db.serverDate() } }); }物品录入时选择一个叶子位置节点保存物品时把该位置的fullPath冗余进物品记录。这样物品列表页不需要做任何递归操作直接展示物品卡片上的“位置”字段就知道去哪个柜子找。物品录入页我设计成了一张卡片表单名称、数量、单位、分类、位置选择、拍照、备注。其中数量是重点检查项用户最容易空着或者填0我在前端处理了默认值逻辑数量为空默认1数量填了0就提示“物品数量至少为1”。这个看起来细枝末节但在使用体验上很关键因为物品清单的核心价值就是“有什么、有多少、在哪儿”。拍照功能用了云存储流程是wx.chooseMedia选择图片压缩后上传到云存储把返回的fileID存进物品记录。列表页直接渲染fileID对应的图片不需要额外建立图片服务器。这里要提醒一句选择图片的时候务必指定压缩参数否则一张原图可能好几MB上传慢不说列表渲染时内存压力也大这个坑后面踩坑章节会专门讲。3.3 备忘录与全局搜索让碎片信息可召回备忘录模块的功能门槛不高无非是标题、正文、标签、置顶但有一个关键体验我做得很用心就是“快速记录”。入口做了两个一个是首页的备忘录Tab里放一个固定的顶部输入框点进去直接编辑另一个是首页全局搜索框下面放“最近编辑的笔记”避免用户为了找一条旧笔记反复翻页面。正文编辑我最终选择了“纯文本 轻量Markdown约定”的方案。很多数据库字段都给正文预留了大文本空间但前端怎么编辑反而是一大瓶颈。我试过引入第三方富文本编辑器包体积涨了将近200KB编辑体验在手机上也不理想。最后干脆用textarea约定#表示标题、-表示列表项、**表示加粗。渲染时用轻量正则转成行内样式既满足了记录需求又把包体积控制住了。全局搜索是把这个系统价值最大化的功能。搜索页一个输入框防抖300毫秒后同时搜三个集合用数据库正则做包含匹配function debounce(fn, delay 300) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() fn.apply(this, args), delay); }; } const onSearch debounce(async (keyword) { if (!keyword.trim()) { setData({ list: [] }); return; } const reg db.RegExp({ regexp: keyword, options: i }); const [todoRes, itemRes, noteRes] await Promise.all([ db.collection(todos).where({ title: reg }).limit(20).get(), db.collection(items).where({ name: reg }).limit(20).get(), db.collection(notes).where({ title: reg }).limit(20).get() ]); setData({ list: [ ...todoRes.data.map(item ({ ...item, type: todo })), ...itemRes.data.map(item ({ ...item, type: item })), ...noteRes.data.map(item ({ ...item, type: note })) ] }); }, 300);搜索结果的排序规则是事务按截止时间倒序、物品按更新时间倒序、笔记按置顶和更新时间倒序。所有结果混合展示每条前面带一个类型标签点击进入对应详情页。这个搜索页做出来以后我日常使用频率极高找东西、找文档、查待办基本都从这里进。4. 数据安全、性能与体验细节4.1 云开发数据库权限与用户隔离云开发数据库默认的权限模板有好几种“所有用户可读仅创建者可写”“仅创建者可读写”等等。很多教程会让你直接选“所有用户可读”理由是省事开发阶段调试方便。这种做法在练习demo里没问题但一旦系统真的承载个人数据就是灾难。我给四个集合都配置了自定义安全规则核心就是一条每个用户只能对自己_openid对应的记录有读写权限。{ read: doc._openid auth.openid, write: doc._openid auth.openid }这里有个安全细节容易忽略新增记录时_openid字段是由云开发帮你自动注入的用户没法自己伪造。但如果你的小程序页面通过db.collection(todos).add()新增记录时有意识地传入了_openid字段就会破坏这套安全模型。正确做法是在前端新增数据时不传_openid让云开发自己填。我在代码review时专门检查过这一点确保所有add调用都不带_openid字段。另外如果以后系统要升级成“家庭成员共享”安全规则要重新设计从“按openid隔离”改成“按家庭ID 成员角色控制”。这正是我在做权限设计时留的扩展点用户表里预留了一个familyId字段但现在先留空不启用共享逻辑。4.2 统一请求封装与错误处理小程序项目里调用云函数的入口很多事务增删改查、物品增删改查、搜索、位置树查询每个页面都在调。如果每个地方都写一套wx.cloud.callFunction代码会特别散出错了也不知道打到哪。我抽了一个utils/request.js做统一封装。// utils/request.js const callFunction (options) { wx.showLoading({ title: options.loadingText || 加载中, mask: true }); return wx.cloud.callFunction({ name: options.name, data: options.data || {} }).then(res { wx.hideLoading(); const result res.result; if (result result.success false) { wx.showToast({ title: result.message || 操作失败, icon: none }); return Promise.reject(result); } return result; }).catch(err { wx.hideLoading(); console.error(云函数调用失败, options.name, err); wx.showToast({ title: 网络异常请稍后重试, icon: none }); return Promise.reject(err); }); }; module.exports { callFunction };所有云函数返回统一结构{ success: true, data: xxx }或者{ success: false, message: xxx }前端一处统一处理错误提示。这样页面里调用就非常干净const res await callFunction({ name: addTodo, data: { title: 买机票, dueTime: 2025-02-14 10:00:00 } });还有一个细节微信小程序的wx.cloud.callFunction默认没有超时重试偶尔网络抖动会报错。我在封装里加了一个简单的重试机制失败后在1秒后重试一次第二次仍失败才抛异常。个人项目里这条小优化提升了不小稳定性。4.3 缓存策略哪些数据放本地、哪些从云端拉云开发数据库每次查询都要走网络页面上来回切Tab频繁触发查询会让体验大打折扣。缓存策略我的设计原则是低频变化的数据放本地高频变化的数据走云端。数据缓存策略原因位置树本地缓存7天位置变更频率低查询频率高事务分类列表本地缓存30天固定枚举值物品列表云端每次拉取本地仅做立即缓存物品可能频繁变动需实时事务列表云端每次拉取状态变化频繁备忘录列表云端每次拉取需要记录最新修改时间用户设置本地长期缓存纯本地配置本地缓存我用带时间戳的封装比如位置树缓存7天取缓存时先判断是否过期不然会用到“过期退不掉的旧位置结构”// utils/storage.js function setCache(key, data, expireSeconds) { const obj { expireAt: Date.now() expireSeconds * 1000, data }; wx.setStorageSync(key, obj); } function getCache(key) { const obj wx.getStorageSync(key); if (!obj) return null; if (obj.expireAt obj.expireAt Date.now()) { wx.removeStorageSync(key); return null; } return obj.data; }另外有一个容易踩的小细节是自定义导航栏。如果小程序页面用了自定义导航栏右上角胶囊按钮还在需要动态计算状态栏高度和胶囊按钮的位置否则在刘海屏手机上线会顶到摄像头。我在utils/system.js里封装了获取顶部导航栏高度的逻辑计算思路是“状态栏高度 胶囊按钮高度 上下间距”并在自定义导航栏组件里统一处理。4.4 列表分页、图片懒加载与首次加载体验个人数据虽然通常不大但事务列表和备忘录列表积攒半年后也能上百条。我的列表页统一用onReachBottom触发加载下一页每页拉取20条。云数据库skip方法在数据量大时会有些性能退化但个人项目数据量最多几千条够用了。图片加载是我特别在意的一个点。物品卡片上会显示缩略图如果图片多且全部立刻加载列表滚动时会明显掉帧。我在图片标签上开启了懒加载image src{{item.photo}} lazy-loadtrue modeaspectFill /首次进入各模块时页面在数据加载完成前展示骨架屏而不是白屏或者一个转圈。这个体验细节虽然简单但对“个人管理工具”这类使用频率高、每次停留时间短的应用来说非常关键用户每次打开都想快速看到结果骨架屏能降低等待焦虑。5. 踩坑实录从白屏到数据“消失”的完整排错链路5.1 iOS下时间字符串解析导致页面白屏现象是安卓手机和开发者工具一切正常但iPhone上打开事务列表直接白屏控制台报错Invalid Date。排查过程我分了三步。第一步先给可能崩溃的位置加console.log定位是列表页渲染事务时间字段崩了。第二步怀疑是new Date()解析的问题我在iOS真机上用safari调试console手动执行时间转换发现iOS的JavaScript引擎不支持new Date(2025-02-14 10:00:00)这种“短横线加空格”的格式必须用2025/02/14 10:00:00这种正斜杠格式。第三步把代码里所有时间转换增加统一兼容函数// utils/date.js function parseDate(str) { if (!str) return new Date(); if (typeof str object) return str; // iOS不支持短横线格式替换成斜杠 const normalized str.replace(/-/g, /); return new Date(normalized); }这个坑非常典型很多新手做小程序在开发者工具里测得好好的一上真机就崩。如果你也在做类似系统建议所有日期字符串解析一律先做格式归一化别指望iOS自动兼容。5.2 订阅消息授权次数用完事务提醒静默失效系统上线后用了两周我忽然发现自己收不到事务提醒了。数据库里明明有记录云函数定时触发器也在跑可就是没消息。我先在云函数本地手动测试了一次subscribeMessage.send发现能成功发送说明模板ID和openid对得上。再查定时触发器配置发现触发器确实在跑但可能因为提醒时间点已经过去被前一次执行跳过了。这两步都没查出问题后我翻阅了微信官方文档终于锁定了根因订阅消息的授权是按次计算的用户同意一次只能接收一条消息发完就作废。我创建事务时订阅了一次提醒发出去就耗尽了授权后续再有新事务要提醒通知根本发不出去。解决办法是在设计上把这个规则彻底融入流程。用户在创建待办时如果设置提醒时间我会先读取本地记录的subCount剩余次数如果剩0就主动弹一次订阅请求如果用户连续拒绝我会在页面上显示一个“提醒可能失效”的提示并引导用户去“支付页”不是“设置”里开启订阅消息授权。同时云函数的定时触发器配置成每两分钟执行一次保证提醒时间落在一个宽松的时间窗口内避免因为触发粒度太粗而漏发。这套方案上线后提醒可靠性从“随缘”变成了“基本准时”。这里我深刻意识到微信订阅消息的“一次性授权”规则对开发者非常不友好所以在设计产品流程时一定不要把“用户授权”视为理所当然要有一个“剩余订阅次数”的状态管理并给用户足够的透明度。5.3 安全规则误配导致数据“消失”有一次我在测试机上用另一个微信账号登录发现事务列表和物品列表全空而主账号数据都正常。第一反应是数据丢了吓得我赶紧去云开发控制台查结果数据库里一条记录都没删。问题出在安全规则上。我之前为了调试方便把某个集合的权限设置成了“所有用户可读”后来改回自定义规则时安全规则的语法写错了导致查询请求被后端拒绝前端把这解释成了空数组。具体处理路径是在微信开发者工具的云开发控制台逐个检查集合的安全规则确认read和write规则都是doc._openid auth.openid并在开发工具中用不同的openid模拟登录反复验证不同账号之间确实互不可见、自己的数据可见。这个坑的教训是安全规则的修改不要直接在生产环境碰先在云开发控制台小范围验证。而且权限配置完成后至少要用两个不同的微信号各做一遍完整流程否则很容易被“单账号看起来没问题”的假象蒙蔽。5.4 图片选择原图导致内存暴涨与页面卡顿物品拍照上传功能刚做完时我在微信开发者工具里测试一切正常。后来在真机上测试选了一张相机拍摄的接近10MB的照片上传后物品列表直接卡成PPT最后小程序崩溃重启。排查过程先看控制台内存曲线发现wx.chooseMedia选择原图后临时文件路径对应的是几MB到十几MB的图片上传到云存储虽然能成功但列表页渲染图片时一次性加载了太多大图手机内存瞬间被打满。解决方法是分两层第一层选择阶段就指定压缩参数wx.chooseMedia({ count: 1, mediaType: [image], sizeType: [compressed], sourceType: [album, camera], success(res) { const tempFilePath res.tempFiles[0].tempFilePath; // 后续上传到云存储 } });第二层云存储里保存原图和压缩图两个版本实际上sizeType: [compressed]拿到的是压缩图上传到云存储后列表直接展示即可普通物品照片的清晰度完全够用。后来我又在列表页给图片加了lazy-load属性进一步降低渲染压力。这个优化做完后列表滚动回归流畅没有再出现过内存崩溃。最后说点实际使用感受。这个系统上线后我自己用了大半年最大变化是找东西不再需要回忆“当时记在哪里”先搜关键词再按位置路径直接定位。事务提醒因为订阅消息机制设计得合理漏掉的情况很少。我更想说的是做这类“小而全”的项目最忌讳一开始就把功能清单拉得很长。把事务、物品、备忘录三个域的数据模型理清楚MVP跑通再把提醒、搜索、体验这类细节叠加上去才有机会做成真正能天天打开的应用。如果后续要扩展我会优先做数据导出和家庭成员共享但前提一定还是先把权限边界和数据隔离做扎实。
返回列表