☰
微信小程序校园失物招领系统实战:云开发与云函数核心设计
2026/9/25 5:02:55 网站建设 项目流程

简介:面向正在准备微信小程序毕业设计或课程设计的学生,这份资源提供了一套完整的校园失物招领系统项目,包含可直接阅读的源码工程、说明文档和演示视频。项目围绕“设计与实现”展开,覆盖了从系统总体设计、数据库设计到前端功能模块落地的全过程,具体涉及失物招领信息展示、论坛交流、公告管理、失物发布等核心页面,并配有系统测试的目的与测试方法说明,有助于读者理解小程序前后端交互及常规开发规范。目录层次清晰,可借鉴其中的界面布局与数据库表设计思路。资料打包为rar格式,大小约38.82MB,内含项目源码、Word版设计文档以及操作演示视频,方便对照文档查看代码结构或按视频复现运行效果。目前已有120人学习,适合需要完整项目参考、快速梳理毕业设计框架的开发者。

1. 基于微信小程序的校园失物招领系统:比想象中更值得做的实战项目

校园失物招领这个场景,绝大多数人的第一反应是“太简单了”。但真去接手一个基于微信小程序的校园失物招领系统,你会发现它并不是「发布信息 + 展示列表」两件事就能糊弄过去的。我见过太多人把精力花在页面好不好看上,结果栽在图片上传、状态流转、重复认领这些看不见的环节上。这个项目真正的价值,在于它把小程序端、云开发后端、数据库权限、审核流程串成了一条完整链路,是一个能讲清楚“数据怎么流动”的练手项目。

这个系统适合两类人:一类是正在做课程设计或毕业设计,需要源码和说明文档来支撑答辩的学生;另一类是刚接触小程序开发,想用业务闭环来验证自己能力的从业者。它不追求高并发、不涉及复杂算法,但云函数怎么写、表结构怎么拆、状态机怎么设计,这些经验和写大项目是相通的。标题里提到的源码、说明文档、演示视频,恰恰是把这个项目从“能跑”推向“能交付”的三件套,缺一不可。

2. 从需求到表结构:先把失物招领的业务闭环画清楚

2.1 业务闭环:发布—匹配—认领—核销,四个状态缺一不可

很多人做失物招领系统,第一版只做两张表:一张存失物,一张存招领,用户自己刷列表碰运气。这样做不是不能跑,但离“系统”两个字差得很远。真实的校园场景里,丢东西的人希望快速找到,捡到东西的人希望尽快脱手,管理员希望减少无效沟通。这三方的诉求叠加起来,业务至少要包含四个环节:发布、匹配、认领、核销。

发布环节要区分「寻物启事」和「失物招领」两种类型,它们的浏览逻辑不同、认领逻辑也不同。寻物启事是失主主动描述物品特征,等捡到的人联系;失物招领是拾主上传物品照片,等失主来认领。匹配环节不能只靠人肉刷新,至少要有按关键词搜索和按分类筛选的能力。认领环节是防冒领的关键,不能谁点一下“我要认领”就把东西拿走,必须加一层描述核对或管理员审核。核销环节则是把物品状态从“待认领”改成“已完成”,同时记录操作时间,方便日后追溯。

这四个环节落实在系统里,就是一张状态流转图:发布(1)→ 匹配(2)→ 认领(3)→ 核销(4)。状态不能只在代码里用 if 判断,必须在数据库里落一个字段,比如status,用整数表示:0 为待处理、1 为已认领待审核、2 为已核销、3 为已下架。这样后续做列表过滤、管理员统计、演示视频里的流程展示,都只需要查这一个字段。

2.2 数据库设计:用户表、物品表、认领表、审核日志表的具体字段

如果把系统做成微信云开发方案,数据库直接用云开发自带的 JSON 文档数据库,不需要自己搭 MySQL。这个选择对毕设和课设尤其友好:不用买服务器、不用配域名备案、天然支持微信登录。但你依然需要像设计关系型数据库一样,提前把集合(表)和字段定清楚,否则写到一半改数据结构,云函数里的查询逻辑会跟着返工。

我一般会拆四张核心集合。第一个是用户集合users,字段包括openid(用户唯一标识,云开发下建议直接用云函数获取)、nickName、avatarUrl、studentId(学号,选填)、phone(选填,用于认领联系)、createTime。注意openid不要暴露在小程序端直接写入,而是在云函数里用cloud.getWXContext().OPENID获取,这样能防止用户伪造身份。

第二个是物品集合items,也就是系统的核心数据。字段设计如下:itemId(我用_id自动生成)、type(1 寻物 / 2 招领)、title(物品名称)、category(分类,比如校园卡、钱包、耳机、书籍)、description(详细描述,这里要引导用户写特征,为认领核对做准备)、images(数组,存云存储 fileID 列表)、location(丢失或拾得地点)、time(丢失或拾得时间)、status(状态,0 待处理 / 1 待审核 / 2 已完成 / 3 已下架)、openid(发布者)、contact(联系方式)、createTime。这里有一个容易被忽略的点:time这个字段名在云开发里做排序时和前端传入格式容易出歧义,建议直接用happenTime表示丢失或拾得时间,避免和createTime混淆。

第三张是认领集合claims,记录每一次认领申请。字段有itemId、claimOpenid、description(认领者填写的物品特征描述)、status(0 待审核 / 1 通过 / 2 拒绝)、createTime、reviewTime。第四张是审核日志集合reviews,记录管理员的每一次操作,字段包括itemId、action(pass / reject / finish)、reviewerOpenid、remark、createTime。日志集合看起来多余,但写说明文档时它是亮点,答辩时也是交代“系统可追溯性”的抓手。

如果你选择自建后端路线,用 MySQL 的话,四张表对应关系不变,只是把集合名换成表名,把 JSON 字段换成 SQL 字段。下面给一份 MySQL 版的核心建表脚本,便于对照:

CREATE TABLE items ( id INT PRIMARY KEY AUTO_INCREMENT, type TINYINT NOT NULL COMMENT '1招领 2寻物', title VARCHAR(100) NOT NULL, category VARCHAR(50) DEFAULT '', description TEXT, images JSON COMMENT '图片URL数组', location VARCHAR(100), happen_time DATETIME, status TINYINT DEFAULT 0 COMMENT '0待处理 1待审核 2已完成 3已下架', openid VARCHAR(64) NOT NULL, contact VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE claims ( id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, openid VARCHAR(64) NOT NULL, description TEXT, status TINYINT DEFAULT 0 COMMENT '0待审核 1通过 2拒绝', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, review_time DATETIME DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

images字段用 JSON 类型存储,能省掉一张附件表,这是多数人实践后的常见做法。用 utf8mb4 是为了兼容用户输入的生僻字和 Emoji,微信昵称里常有特殊字符,这个坑在联调时才会暴露。建表后记得给items.status和items.openid加索引,列表查询和我的发布页都用得到。

2.3 图片存储选型:为什么优先用微信云存储而不是自建文件服务器

校园失物招领系统的图片存储,是很多初学者第一道坎。有人图省事把图片转成 base64 直接存数据库,结果列表页渲染卡到怀疑人生;有人坚持自建 Nginx + FastDFS,折腾两天还没把上传接口调通。对这个项目体量来说,微信云开发的云存储是最务实的方案。

云存储的优点在于:第一,和云函数、云数据库同属一套环境,前端直接调用wx.cloud.uploadFile就能把图片传上去,返回一个 fileID;第二,fileID 可以直接在小程序端用image组件的src属性渲染,不需要额外做鉴权,云开发会自动处理;第三,不用考虑图片服务器的带宽和磁盘扩容问题。你唯一要做的,是在云开发控制台里给云存储设置一个合理的权限。默认权限建议改成「仅创建者可读写」,因为失物招领的图片会包含个人物品信息,如果允许所有人读,数据库里存了 fileID 就等于所有人能看图片,不合适。

自建文件服务器倒也不是不行,但你要额外处理跨域(CORS)、防盗链、HTTPS 证书、图片压缩,这四件事每一件都能消耗两三天,而且和微信小程序的对接还得走业务域名配置。标题给的定位是「源码 + 说明文档 + 演示视频」,说明这是一个以交付为导向的项目,云存储能把交付周期从三周压到一周。核心权衡点在表里:

对比维度微信云存储自建 Nginx / FastDFS
部署成本零部署,开通即用需要服务器,配 Nginx 和上传接口
权限控制控制台配置,按 openid 粒度需要自己写鉴权逻辑
小程序鉴权fileID 直接渲染需配域名 + HTTPS,处理防盗链
适合场景毕设、课设、原型验证有现成服务器和运维能力的场景

3. 小程序端三个必写页面:发布页、列表页、详情认领页

3.1 发布页:图片上传的坑与 wx.chooseMedia 参数配置

发布页是用户接触系统的第一个入口,也是代码里最容易出问题的页面。核心功能就两个:填物品信息、上传图片。图片上传的官方接口是wx.chooseMedia,它替代了旧版的wx.chooseImage。很多人写到这里,只配了count和mediaType就完事,结果真机上一选相册就白屏,原因多半是sourceType或sizeType没配对。

// pages/publish/publish.js Page({ data: { imageList: [], type: '1', // 默认选择"失物招领",1=招领 2=寻物 title: '', description: '' }, // 选择图片 chooseImages() { wx.chooseMedia({ count: 3 - this.data.imageList.length, // 最多3张,减去已选数量 mediaType: ['image'], sourceType: ['album', 'camera'], sizeType: ['compressed'], // 强制压缩,避免原图过大上传失败 success: (res) => { const selected = res.tempFiles.map(file => file.tempFilePath); this.setData({ imageList: this.data.imageList.concat(selected) }); } }); }, // 上传图片到云存储,返回 fileID 数组 async uploadImages() { const promises = this.data.imageList.map((filePath, index) => { const cloudPath = `lost/${Date.now()}-${index}-${Math.random().toString(36).slice(-6)}.jpg`; return wx.cloud.uploadFile({ cloudPath, filePath }).then(res => res.fileID); }); return Promise.all(promises); } });

这段代码里的sizeType: ['compressed']是关键。校园用户用手机拍失物照片,原图可能是 3MB 以上的大图,直接上传到云存储会慢、偶尔还会超时失败。压缩后一般 200KB 以内,上传速度可以接受,列表页渲染也更流畅。cloudPath里拼了时间戳和随机字符串,是为了避免同一用户反复上传时文件名冲突——云存储的cloudPath如果重复,后传的文件会覆盖先传的,这个坑我就踩过。

上传后的 fileID 不要直接放到items集合里,要先调用云函数写入数据库。这样做的原因是:前端直接把数据写到数据库,绕过了云函数的权限校验,用户可以把status改成任意值,等于给自己开了后门。正确姿势是前端收集表单数据后,调wx.cloud.callFunction({ name: 'publishItem', data: {...} }),由云函数统一校验入参并写入数据库。版权声明可以不放,但代码里一定要保留入参校验和openid获取的逻辑。

3.2 列表页:下拉刷新与触底分页的经典写法

列表页是失物招领系统的门面,它的表现直接决定用户留不留。技术上要处理的就两件事:分页加载和下拉刷新。很多人第一次写云开发数据库查询,习惯直接db.collection('items').get()一把梭,结果数据量过了几百条就变慢。云开发的默认限制是一次最多返回 20 条,不是 100 条,导致后面数据永远加载不出来。正确做法是配合skip和limit实现分页,再利用小程序原生的onPullDownRefresh和onReachBottom两个生命周期处理刷新和加载更多。

// pages/index/index.js Page({ data: { items: [], page: 0, pageSize: 10, hasMore: true, keyword: '' }, async loadItems(reset = false) { if (reset) { this.setData({ page: 0, items: [], hasMore: true }); } if (!this.data.hasMore) return; const db = wx.cloud.database(); const { page, pageSize, keyword } = this.data; // 关键词搜索,正则模糊匹配标题 const where = { status: db.command.neq(3) // 排除已下架 }; if (keyword) { where.title = db.RegExp({ regexp: keyword, options: 'i' }); } const res = await db.collection('items') .where(where) .orderBy('createTime', 'desc') .skip(page * pageSize) .limit(pageSize) .get(); const newItems = reset ? res.data : this.data.items.concat(res.data); this.setData({ items: newItems, page: page + 1, hasMore: res.data.length === pageSize }); }, onPullDownRefresh() { this.loadItems(true).then(() => wx.stopPullDownRefresh()); }, onReachBottom() { this.loadItems(); } });

注意page的自增时机是在拿到数据之后,不是请求之前,否则失败重试时会跳过一页,导致数据漏掉。db.RegExp是云开发端提供的正则查询方式,做标题模糊搜索够用,但不要用它做description字段的匹配——正则查询不支持索引,数据量大时会有性能问题。status: db.command.neq(3)这个写法,只在items集合上生效,如果你用的是 MySQL 后端,等价写法是SELECT ... WHERE status != 3。这里能看出来,云开发数据库的查询 API 学习成本不高,但一些边界条件(比如neq是db.command下的方法)容易记混。

下拉刷新还有一个容易被忽略的点:app.json里要开启"enablePullDownRefresh": true,并且建议在window配置里设"backgroundTextStyle": "dark",否则 iOS 下拉刷新的转圈是白色的,白底上看不见,用户会以为刷新功能坏了。

3.3 详情页与认领流程:用状态机驱动按钮渲染

详情页是认领流程的核心载体。一张物品卡片上有发布者信息、物品描述、图片、联系方式和操作按钮。按钮怎么渲染不能写死,得根据当前用户和物品状态动态计算。比如物品是自己的,显示“删除”和“标记已完成”;物品是别人的且状态为待处理,显示“申请认领”;已经提交过认领申请,显示“审核中”。这种多分支渲染,最清晰的做法是先梳理状态矩阵,再写wx:if。

// pages/detail/detail.js Page({ data: { item: null, claimStatus: -1, // -1 未申请,0 待审核,1 已通过,2 已拒绝 isOwner: false }, // 计算按钮状态 buildActions() { const { item, claimStatus, isOwner } = this.data; const actions = []; if (isOwner) { if (item.status === 0 || item.status === 1) { actions.push({ key: 'finish', text: '标记已完成' }); } if (item.status !== 2) { actions.push({ key: 'remove', text: '下架物品' }); } } else if (item.status === 0) { if (claimStatus === -1) { actions.push({ key: 'claim', text: '申请认领' }); } else if (claimStatus === 0) { actions.push({ key: 'pending', text: '审核中', disabled: true }); } else if (claimStatus === 1) { actions.push({ key: 'contact', text: '联系发布者' }); } else { actions.push({ key: 'claim', text: '重新申请' }); } } this.setData({ actions }); } });

claimStatus是查claims集合得到的当前用户对这个物品的申请状态。这里有一个先后顺序问题:必须等item和claimStatus都拿到后再渲染按钮,否则用户会看到按钮闪烁一下,体验很糟。常见的做法是在onLoad里并行发起两个请求,用Promise.all聚合后再调buildActions()。

认领申请的动作要在云函数里完成,前端不能直接写claims集合。云函数至少要做三件事:检查物品状态是否为 0,防止已完成的物品被重复申请;检查该用户是否已申请过,防止重复提交;写入认领记录并将物品状态改为 1(待审核)。这三步在云函数里按顺序执行,即使前端被用户反复点击,也能通过数据库的状态约束挡住。

4. 后端接口与核心逻辑:云函数怎么写才不返工

4.1 云函数和自建后端的取舍:数据权限与调用成本

微信云开发模式下,后端逻辑主要写在云函数里。一个云函数对应一个exports.main入口,相当于一个 HTTP 接口。和传统自建后端比,云函数最大的特点是不需要关心服务器,但代价是冷启动延迟和调用次数限制。对失物招领这种低频业务,冷启动几乎无感,免费额度也足够。真正影响选型的是数据权限。

云开发数据库有三种权限设置:仅创建者可读写、所有用户可读仅创建者可写、所有用户可读写。失物招领系统里,items集合建议用「所有用户可读,仅创建者可写」。因为列表页和详情页的查询是高频操作,如果都走云函数,每次都调用一次云函数,不仅慢,还会消耗云函数调用次数。但claims集合必须收紧权限,认领记录属于敏感数据,不能让所有用户读。你需要保证“读公开数据走数据库直连,写数据走云函数校验”。这是一个混合策略,也是大多数线上小程序的实际写法。

自建后端则是把所有读写都收口到自己的接口。优点是逻辑看得见摸得着,调试手段更丰富;缺点是要处理登录态校验、用户身份识别。微信小程序要自建后端,通常流程是wx.login拿 code,后端用 code 换 openid,然后签发自定义 token。这套流程在毕业设计里也能做,但涉及 HTTPS 证书、code2session 接口调用,工作量直接翻倍。如果不是为了练习 Java 或 Node 后端,云函数是更划算的选择。

4.2 关键词匹配与模糊查找:让用户快速找到物品的查询写法

失物招领信息一多,光靠列表滚动没法用,必须做搜索。搜索的核心是关键词匹配用户输入的物品名称。微信云开发数据库支持db.RegExp,可以用正则表达式做模糊匹配。但要注意,正则匹配在数据量超过几千条后性能会明显下降,所以搜索条件里要配合其他筛选条件一起用,比如加上status过滤和category分类。

下面这个云函数实现了扫码式搜索:接收keyword,先按标题模糊匹配;标题匹配不到,再用description做全文匹配。两个查询并行发起,结果合并去重后返回。去重逻辑用itemId做 map 的键,一劳永逸避免重复。

// cloudfunctions/searchItems/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 (event) => { const { keyword = '', category = '', page = 0, pageSize = 10 } = event; const reg = db.RegExp({ regexp: keyword, options: 'i' }); try { // 并行查标题和描述,减少响应时间 const [titleRes, descRes] = await Promise.all([ db.collection('items') .where({ title: reg, status: _.neq(3), ...(category ? { category } : {}) }) .limit(pageSize) .get(), db.collection('items') .where({ description: reg, status: _.neq(3), ...(category ? { category } : {}) }) .limit(pageSize) .get() ]); // 合并去重 const map = new Map(); [...titleRes.data, ...descRes.data].forEach(item => { if (!map.has(item._id)) { map.set(item._id, { ...item, matchField: item.title.match(reg) ? 'title' : 'description' }); } }); return { ok: true, data: Array.from(map.values()).slice(0, pageSize), count: map.size }; } catch (err) { return { ok: false, message: err.message }; } };

Promise.all在这里的意义不是炫技,而是把两次串行查询变成并行,省一半时间。matchField字段用于前端展示时高亮匹配位置,虽然高亮不是必须的,但说明文档里写清楚“搜索结果可识别匹配字段”,答辩时能体现设计的完整性。db.RegExp的对象不能直接展开到扩展运算符里,必须嵌套在where条件内,否则查询不会生效。这一点卡过不少人。

4.3 认领核销与防冒领:用状态流转和操作日志兜底

认领流程是失物招领系统的核心,也是最容易被简化处理的部分。简化版的实现是:用户在详情页点“认领”,把items.status改成 2(已完成),然后完事。这个流程在演示视频里看起来很流畅,但实际使用时,任何人都能点一下就把别人的东西“领走”,冒领问题无法避免。

我在实际项目中用的方案是“描述核对 + 管理员审核”双保险。用户在详情页填一段描述,说明物品的特征细节,比如校园卡上的姓名拼音、钱包的颜色和品牌。云函数收到后,把这条记录写入claims集合,同时把items.status改为 1(待审核)。发布者在“我的发布”里看到待审核状态后,能查看认领者填写的描述,对照自己的物品特征决定通过或拒绝。如果发布者没空处理,管理员也能在管理端介入。

这套流程用云函数实现时,关键在于对并发提交的防护。用户连续点击两次“申请认领”,可能产生两条认领记录,导致物品状态错乱。解决方法是云函数内部用事务。云开发支持db.runTransaction,是处理分布式事务的推荐方式,能在并发场景下保证数据一致性。下面是我常用的原子化认领写法:

// cloudfunctions/claimItem/index.js const cloud = require('wx-server-sdk'); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db = cloud.database(); exports.main = async (event) => { const { OPENID } = cloud.getWXContext(); const { itemId, description } = event; try { return await db.runTransaction(async transaction => { const item = await transaction.collection('items').doc(itemId).get(); // 双重校验:物品必须存在且状态为 0 if (!item.data || item.data.status !== 0) { return { ok: false, message: '物品不存在或已被认领' }; } // 校验用户是否重复申请 const claim = await transaction.collection('claims') .where({ itemId, openid: OPENID, status: _.neq(2) }) .get(); if (claim.data.length > 0) { return { ok: false, message: '请勿重复申请' }; } // 写入认领记录并更新物品状态 await transaction.collection('claims').add({ data: { itemId, openid: OPENID, description: description || '', status: 0, createTime: db.serverDate() } }); await transaction.collection('items').doc(itemId).update({ data: { status: 1 } }); return { ok: true, message: '认领申请提交成功' }; }); } catch (err) { return { ok: false, message: err.message }; } };

runTransaction的写法和普通云函数明显不同,它把所有操作放在transaction对象上执行。如果中途任一步出错,事务会自动回滚,不会出现“认领记录写入了,但物品状态还是待认领”这种脏数据。事务内的get是读取当前版本的数据,能避免两个人同时提交时都通过了status !== 0的判断。这段代码是说明文档里最好的素材,也是答辩时“系统设计考虑了并发安全”的有力证据。

5. 避坑合集:图片、缓存、状态不同步的五个经典踩坑记录

5.1 图片上传失败,错误码却是 success

现象:wx.chooseMedia回调显示成功,uploadFile也返回了fileID,但详情页图片加载不出来,控制台报 404。这个问题的隐蔽之处在于,失败不是上传环节,而是cloudPath包含了中文或特殊字符。某些版本的云存储对中文路径处理不友好,会静默失败或返回一个无法访问的 fileID。解决:cloudPath只用字母、数字、横杠和点。我建议用time + random + ext的格式,扩展名从filePath里取,不要写死.jpg,因为用户可能拍的是 png 或 heic 格式。

另外注意,wx.uploadFile一次只能传一个文件,如果选择多图,必须用Promise.all并发上传,但并发数不要超过 3。超过 3 个文件同时上传,部分手机在弱网环境下会超时,表现是“上传了但列表里少图”。处理办法是失败重试,重试次数控制在 2 次以内,同时给用户提示“图片上传失败,请检查网络”。

5.2 列表页滑动快了会跳数据

现象:快速下拉或连续触底时,页面出现重复的卡片,或者几个列表项消失。原因:分页用的是page计数,但每次触底时page的递增发生在异步请求返回之后,用户快速滚动时会发出多次请求,多个请求的skip参数相同,自然返回同一批数据。解决:在data里加一个isLoading锁,请求开始时置为true,请求结束后置为false;onReachBottom里先判断锁,锁开着就直接return。这是列表分页的常备武器,任何小程序项目都适用。

5.3 认领状态前端一直不刷新

现象:用户在详情页提交认领,返回后再次进入详情页,按钮还是“申请认领”,没有变成“审核中”。原因:详情页的onLoad只会在页面首次加载时执行,从详情页返回列表页再进入,页面实例被缓存,onLoad不再触发。这是小程序经典的“页面栈缓存”问题。解决:把状态查询逻辑从onLoad挪到onShow中,这样每次页面显示时都会重新拉取claimStatus。代价是多一次请求,但对低频的详情页来说完全可以接受。

5.4 演示视频里最容易翻车的:初始化数据太随意

现象:演示视频录到一半,发现列表页没有数据;或者在“认领”环节,查不到已经发布的物品。这个翻车多数不是程序 bug,而是演示前没有准备演示数据。数据库里空荡荡,功能再完整也显得空洞。解决:写一个初始化云函数,一次性插入 10~15 条模拟数据,覆盖不同状态(待处理、待审核、已完成)和不同分类。模拟数据的createTime要错开,比如最近一小时几条、最近一天几条、最近一周几条,这样列表页下拉刷新和分页都有内容可展示。视频录制前先跑一遍初始化脚本,比现场造数据稳妥得多。

5.5 管理员审核并发点击,状态记录错乱

现象:管理端同时打开多个详情页,对同一条物品的认领申请连续做“通过”操作,最后物品状态变成了“已完成”,但认领记录却有两条通过。原因:管理端的审核逻辑如果写在页面里直接调数据库,就没有事务保护的。并发环境下,两个请求都读到了“待审核”状态,都执行了通过操作。解决:参考 4.3 的事务写法,把审核动作也收口到云函数。审核云函数里用transaction读取认领记录和物品状态,先判断claim.status === 0再更新,从根上杜绝重复审核。管理后台的代码量不大,但这一步不能省。

6. 演示视频与说明文档的落地技巧:让交付物真正为你加分

源码、说明文档、演示视频三件套里,演示视频是最容易被忽视的。很多人临近答辩才拿手机对着屏幕随手一录,开头卡顿、中间翻车、结尾黑屏,功能再全也抵不过观感差。我的习惯是:先写视频脚本,再录内容,最后做剪辑。脚本按功能模块划分,每个模块控制在 30~45 秒,全片不超过 5 分钟。开场 10 秒展示小程序首页列表和下拉刷新,接着演示发布流程,上传图片和填写表单,然后是搜索功能,输入一个关键词看结果高亮,第三段演示认领闭环——提交申请、管理员审核、状态变为已完成。每一段前加一个字幕条,说明当前演示的功能点,观看者不用猜你在干什么。

录制时注意三个阶段的数据准备:第一阶段列表有 5 条未处理的信息;第二阶段新增一条寻物,图片清晰;第三阶段用两个微信号配合,一个发招领,另一个申请认领,模拟真实用户互动。如果只有一个微信号,就通过云开发控制台直接修改数据库里claims的status字段来展示审核效果,管理员审核页面的操作仍然走真实界面。这个“半真半假”的录法在演示视频里是常见做法,功能是真的,只是状态靠后台配合。

说明文档的重点放在系统架构图、数据库设计、核心流程时序图这三块,不用追求大而全的八股文。架构图里把小程序端、云函数、云数据库、云存储四层画清楚,标注每个请求的路径:前端通过wx.cloud.callFunction调用云函数,云函数操作数据库和存储。数据库设计部分放表格,列出每张集合的字段、类型、含义。核心流程时序图画“发布→搜索→认领→审核→核销”的完整链路,一个图顶过千字描述。文档写完以后,把 3.3 的状态矩阵和 4.3 的事务代码单独摘出来放在附录里,这两个细节能给文档的深度拉高一个档次。

开源或交付源码时,记得清理app.js里你的env环境 ID,替换成占位符your-env-id,并告诉接收者在哪里配置。很多源码交付出去跑不起来,九成是环境 ID 还是原作者的,用户自己的云环境根本没有那个 ID 对应的集合。把初始化脚本、云函数目录结构、环境配置三步写进 README 的第一屏,比写什么“项目介绍”都实用。这也是我交付过几套源码后的血泪经验:代码能不能跑,往往不取决于代码本身,而取决于接收者能否在五分钟内完成环境配置。希望这些经验能帮你在交付和答辩时少走弯路,祝你顺利跑通整套流程。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询