从零到一:用微信云开发打造校园流浪猫管理小程序「紫喵园」

16次阅读
没有评论

一群猫、一部手机、一套云函数——本文记录我如何用微信小程序 + 云开发(CloudBase),搭建一个覆盖「发现、建档、记录、审核、统计、领养、赞助」全流程的校园流浪猫公益小程序,并顺带做了一个独立网页管理后台。

从零到一:用微信云开发打造校园流浪猫管理小程序「紫喵园」

一、为什么做这件事

大学校园是流浪猫最集中的地方之一。猫咪翻垃圾桶、误入宿舍、被投喂不当食物、繁殖失控……而真正科学的管理方式是 TNR(Trap-Neuter-Return,诱捕—绝育—放归)。TNR 的前提是「认识每一只猫」:它叫什么、长什么样、是否绝育、是否打过疫苗、常在哪里出没、跟哪只猫亲近。

但现实是,这些信息散落在各院系同学的朋友圈和聊天记录里,没有沉淀、没有结构、也无法共享。于是我想:能不能用小程序把「校园猫咪的数字化档案」这件事做起来——同学随手拍照上传,管理员审核归档,形成一份可持续维护的校园猫咪图鉴。

这就是 紫喵园 的由来。


二、它长什么样:功能全景

小程序端面向全体同学,核心是「记录 + 共享」:

  • 猫咪档案:浏览校园猫咪图鉴,顶部固定搜索栏,支持按毛色、性别、年龄筛选。
  • 发现猫咪:拍照上传新猫,填写毛色/性别/年龄/绝育/疫苗等信息,提交审核。
  • 发现记录:为已有的猫上传新照片、记录相遇时刻,类似「朋友圈」的瀑布流动态。
  • 编辑提案:用户发现信息有误,可提交修正,管理员审核通过后生效。
  • 便利贴:给猫咪贴文字便签,分享观察与心情。
  • 亲人指数:1~5 星评分,一人一猫一票,实时显示平均分。
  • 猫咪关系:支持伴侣/亲子/兄弟姐妹/好朋友/对头等 7 种关系,亲子关系按性别自动显示为母女/父子等。
  • 特殊状态:已领养 / 去喵星 / 失踪,仅管理员可标记。
  • 数据看板:在校数量、绝育率、命名率、毛色/性别/年龄分布、当日访问量。
  • 审核通知:提交内容通过或拒绝后,通过微信订阅消息通知用户。
  • 赞助支持:投喂罐头页,展示赞赏码与支持者列表。

对管理员来说,手机端审核效率低、看不清大图,于是又做了一个网页管理后台(Vue3 + Element Plus),与小程序共用同一套云开发数据,在浏览器里完成审核、改稿、用户管理、赞助管理、数据看板等操作。


三、技术选型

类别 选择
小程序 微信原生 + TypeScript
UI 组件 Vant Weapp
后端 微信云开发(CloudBase)云函数
数据库 云数据库(NoSQL 文档型)
存储 云存储(图片)
网页后台 Vue3 + Vite + Element Plus + vue-router
Web 端调用 @cloudbase/js-sdk(匿名登录 + 云函数)

之所以选择云开发而不是自建后端,核心原因就一个字:。没有服务器、没有域名备案、没有鉴权体系要自己从零写,云函数即写即用,云数据库免运维,对个人/校园公益项目再合适不过。


四、整体架构

┌─────────────┐        ┌───────────────────────────────────┐
│ 微信小程序    │ ────▶  │  云函数(Node.js + wx-server-sdk) │
│ (原生 TS)    │        │  addCat / reviewRecord / ...      │
└─────────────┘        └──────────┬────────────────────────┘
                                  │ 读写
┌─────────────┐        ┌──────────▼────────────────────┐
│ 网页后台     │ ────▶  │  云数据库  +  云存储             │
│ Vue3 + EP   │        │  cats/records/users/admins...  │
└─────────────┘        └─────────────────────────────────┘

两端各自调用同一批云函数,读写同一份数据。云函数是无状态的 FaaS,靠 wx-server-sdk 访问数据库和存储,天然支持并发。


五、几个值得展开的技术点

1. 审核流:所有内容都要「过审」

所有用户提交(新猫、记录、便利贴、编辑提案)统一走一条状态机:

用户提交 → pending(待审核)
              ├── approve → approved(通过,公开可见)
              └── reject  → rejected(拒绝,附原因)

通过 adminUpdateCatreviewRecord 两个云函数集中处理「通过/拒绝」,并联动微信订阅消息给用户发结果通知。这样既保证了图鉴内容的质量,也防止了广告/垃圾内容污染公共空间。

2. 双通道鉴权:一个函数同时服务小程序和网页

这是整个项目里最有意思的一块。小程序里能拿到 OPENID,但浏览器里拿不到;网页后台又需要一套独立的账号体系。

解决办法是在每个云函数里放一个 requireAdmin(event),支持两条通道:

async function requireAdmin(event = {}) {
  // 网页端:token 校验(独立于小程序用户体系)
  if (event.token) {
    const res = await getCollection('adminTokens')
      .where({ token: event.token }).limit(1).get();
    if (!res.data.length) return null;
    const t = res.data[0];
    if (new Date(t.expiresAt) < Date.now()) return null;
    return { username: t.username, via: 'token' };
  }
  // 小程序端:openid 校验(原逻辑不变)
  const { OPENID } = cloud.getWXContext();
  if (!OPENID) return null;
  const user = await getUserByOpenid(OPENID);
  return user && user.role === 'admin'
    ? { username: user.nickname, via: 'openid' }
    : null;
}

登录则用 Node 内置 crypto 做加盐哈希(不引入任何第三方依赖):

// 存储格式 salt:hash
function hashPassword(password, salt = crypto.randomBytes(16).toString('hex')) {
  const hash = crypto.scryptSync(password, salt, 64).toString('hex');
  return `${salt}:${hash}`;
}
// 校验用 timingSafeEqual 防时序攻击

token 用 randomBytes(32) 生成,存进 adminTokens 集合,7 天过期。Web 端每次调用云函数都会带上 token,过期即拒绝。

3. 多管理员分级:最高管理员 / 子管理员

后台账号分两级:

  • 最高管理员role: super):可以进「子管理员管理」页,新增/删除管理员、改角色、重置他人密码。
  • 子管理员role: admin):能做所有审核/修改操作,但不能管理其他管理员账号。

第一个账号由 initAdmin 一次性创建(配合环境变量密钥 + 仅当 admins 集合为空时生效),自动标记为最高管理员。后续账号都在后台添加。

关键实现是「分级鉴权」:普通管理操作走 requireAdmin(不区分 super/admin),而账号管理走一个更严格的 requireSuperAdmin,回查 admins 集合拿到最新角色再放行。

async function requireSuperAdmin(event = {}) {
  const admin = await requireAdmin(event);
  if (!admin) return null;
  // 仅 role 显式为非 super 时拒绝
  if (admin.role && admin.role !== 'super') return null;
  return admin;
}

同时做了几个保护规则:不能删除/降级自己、至少保留一名最高管理员、删除管理员时同步清理其登录 token。

4. 图片缓存:fileID 到临时链接的两层缓存

云存储里的图片字段存的是 cloud:// 格式的 fileID,浏览器无法直接渲染,需要先转成临时 HTTPS 链接(约 2 小时有效)。小程序端做了本地持久化缓存,网页后台则做了「内存 + localStorage」两层缓存:

export async function resolveImageUrl(fileId: string): Promise
<string> {
  if (!fileId.startsWith('cloud://')) return fileId;
  if (memCache.has(fileId)) return memCache.get(fileId)!;   // 第一层:内存
  const cached = loadStore()[fileId];                        // 第二层:localStorage
  if (cached && cached.expiresAt > Date.now()) return cached.url;
  const res = await callFunction('adminGetImages', { fileIds: [fileId] });
  // 命中后写回两层缓存,TTL 设为 100 分钟(提前过期留余量)
}

这样二次查看秒开,也不会因为临时链接失效而频繁报错。

5. 数据统计

getStats 云函数聚合出在校数量、绝育率、命名率、毛色/性别/年龄分布等指标,前端用图表渲染。绝育率直接体现 TNR 的推进程度,是校园猫管最关心的数字。


六、数据库设计

集合 说明
users 小程序用户(openid、昵称、头像、角色)
cats 猫咪档案(名字、照片、毛色、性别、年龄、健康、状态、位置)
records 发现记录 & 便利贴(猫关联、照片、内容、发布者)
editProposals 编辑提案(猫关联、提案者、修改内容、审核状态)
ratings 亲人指数评分(猫、用户、1~5 星)
relationships 猫咪关系(双方猫、关系类型、备注)
dailyVisits 每日访问量
supporters 赞助支持者(昵称、金额、月份)
feedbacks 用户反馈
admins 网页后台账号(用户名、密码哈希、角色 super/admin)
adminTokens 后台登录 token(7 天有效)

七、踩过的坑

  1. res.result 是 undefined:网页匿名调用云函数时,如果安全规则没把 invoke 设为 true,会返回 EXCEED_AUTHORITYres.result 就是 undefined,前端一旦直接读 res.code 就会报 undefined is not an object。解决:封装 callFunction,对无返回结果统一兜底成 { code: -1, message },并提醒给每个新云函数配好权限。

  2. min-width 的表格列会「吃满」剩余宽度:Element Plus 里,如果只有一列是 min-width(弹性列),它会吞掉所有多余宽度,导致「用户名」这一列看起来特别宽;全改成固定 width 又会让表格占不满屏幕、右边留白。最终方案:让多个内容列都走 min-width,让多余宽度在多列之间分摊。

  3. 密码哈希别自己瞎写:直接 crypto.scryptSync + timingSafeEqual,加盐、防时序攻击一步到位,别用 MD5/SHA 裸哈希。

  4. 删除要「联动」:删除一条记录里的照片,要同时删云存储文件、同步 cats 集合里的引用;删除管理员,要同步清理其 token。NoSQL 没有外键约束,这些一致性得自己在云函数里显式处理。


八、总结与展望

紫喵园把一个很「小而美」的需求做成了一个完整闭环:同学随手记录、管理员高效审核、数据沉淀成档案、统计反哺管理决策。技术上没有炫技,全是「够用就好」的选择——原生小程序 + 云开发 + 一套双通道鉴权 + 一个网页后台,换来的是零服务器成本、极低运维负担。

后续想做的方向:

  • AI 猫咪识别:用 FastAPI + PyTorch 做猫脸/毛色识别,上传照片自动预填档案字段,降低录入门槛。
  • 领养对接:领养状态更完善的流转,连接校外的领养渠道。
  • 分享海报:给每只猫生成可分享的海报,让更多人参与进来。

如果你也对校园流浪猫管理感兴趣,或者想参考这套「小程序 + 云开发 + 网页后台」的架构,欢迎交流。每一只被温柔以待的猫,都值得被记住名字。🐾

正文完
 0
评论(没有评论)