从0到100:基于微信云开发的读书会小程序设计与实现

0 阅读5分钟

系统总体设计

本系统采用微信小程序作为前端载体,后端完全基于微信云开发(WeChat Cloud Base)架构,实现了“Serverless”无服务器模式。

  • 前端:微信小程序原生框架(WXML, WXSS, JavaScript/TypeScript)。
  • 后端逻辑:云函数(Node.js环境),处理核心业务逻辑、权限校验及数据聚合。
  • 数据库:云数据库(JSON文档型,类似MongoDB),存储用户、活动、打卡等数据。
  • 存储:云存储,用于存放活动图片、打卡照片及用户头像。
  • 优势:无需域名备案、无需运维服务器、自动弹性扩容、内网链路安全防DDoS攻击。

功能模块划分

用户端:

活动模块:活动列表展示、详情查看、在线报名、现场签到、活动评价。 打卡模块:打卡项目浏览、每日打卡(图文)、打卡排行、我的打卡记录。 个人中心:我的报名、消息通知、后台管理入口(管理员专用)。

管理端(内置于小程序):

活动管理:发布活动、审核报名、导出数据、活动核销。 打卡管理:配置打卡规则、审核打卡记录、导出报表。 系统管理:管理员权限设置、基础数据配置。 在这里插入图片描述

数据库设计

系统主要包含五个核心实体:用户 (User)、活动 (Activity)、报名记录 (Signup)、打卡项目 (CheckinProject)、打卡记录 (CheckinRecord)。

实体关系描述:

用户与活动是多对多关系,通过报名记录关联。 用户与打卡项目是多对多关系,通过打卡记录关联。 活动由用户(组织者)发布。

字段名类型说明
_idString自动生成,主键
titleString活动标题
descriptionString活动详细介绍
locationString活动地点
start_timeDate活动开始时间
end_timeDate活动结束时间
max_countNumber人数上限
current_countNumber当前报名人数
organizer_idString组织者OpenID
statusString状态:'ongoing', 'ended', 'cancelled'
form_configArray自定义报名表单配置(如是否需要身份证)
create_timeDate创建时间
字段名类型说明
_idString主键
activity_idString关联活动ID
user_idString用户OpenID
user_infoObject快照:昵称、头像
form_dataObject用户填写的自定义表单数据
statusString状态:'pending'(待审核), 'pass'(通过), 'signed'(已签到), 'absent'(缺席)
signup_timeDate报名时间
checkin_timeDate签到时间
字段名类型说明
_idString主键
project_idString关联打卡项目ID
user_idString用户OpenID
contentString打卡文字心得
imagesArray图片URL列表
record_dateDate打卡日期(精确到天)
pointsNumber获得积分
is_verifiedBoolean是否已通过审核

关键业务流程设计

flowchart TD A[用户浏览活动列表] --> B{活动是否已满/结束?} B -- 是 --> C[显示不可报名] B -- 否 --> D[进入详情页并填写表单] D --> E[提交报名请求] E --> F{云函数: 检查库存与资格} F -- 失败 --> G[返回失败提示] F -- 成功 --> H[写入signups集合, 更新活动人数] H --> I[报名成功通知]

subgraph 线下活动现场
J[组织者打开管理端] --> K[扫描用户二维码/输入手机号]
K --> L[云函数: 验证报名状态]
L -- 无效 --> M[提示未报名]
L -- 有效 --> N[更新signups状态为'signed']
N --> O[签到成功]
end

系统实现与核心代码

const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
const db = cloud.database();
const _ = db.command;

// 报名云函数入口
exports.main = async (event, context) => {
  const { activityId, formData, userId } = event;
  
  try {
    // 1. 开启事务 (确保数据一致性)
    const transaction = await db.startTransaction();
    
    // 2. 查询活动剩余名额
    const activityRes = await transaction.collection('activities').doc(activityId).get();
    const activity = activityRes.data;
    
    if (activity.currentCount >= activity.maxCount) {
      throw new Error('活动人数已满');
    }
    
    // 3. 检查是否重复报名
    const existRes = await transaction.collection('signups').where({
      activity_id: activityId,
      user_id: userId
    }).get();
    
    if (existRes.data.length > 0) {
      throw new Error('您已报名该活动');
    }

    // 4. 插入报名记录 & 更新活动人数 (原子操作)
    await transaction.collection('signups').add({
      data: {
        activity_id: activityId,
        user_id: userId,
        form_data: formData,
        status: 'pass', // 默认自动通过,也可设为pending待审核
        signup_time: db.serverDate()
      }
    });

    await transaction.collection('activities').doc(activityId).update({
      data: {
        current_count: _.inc(1)
      }
    });

    // 5. 提交事务
    await transaction.commit();
    
    return { success: true, msg: '报名成功' };

  } catch (err) {
    // 异常回滚
    console.error(err);
    return { success: false, msg: err.message };
  }
};

const app = getApp();

Page({
  data: {
    content: '',
    images: [],
    projectId: ''
  },

  // 选择图片
  onChooseImage() {
    wx.chooseMedia({
      count: 9,
      mediaType: ['image'],
      sourceType: ['album', 'camera'],
      success: (res) => {
        this.setData({ images: res.tempFiles.map(item => item.tempFilePath) });
      }
    });
  },

  // 提交打卡
  async onSubmit() {
    if (!this.data.content) return wx.showToast({ title: '请填写心得', icon: 'none' });

    wx.showLoading({ title: '提交中...' });

    try {
      // 调用云函数处理图片和入库
      const result = await wx.cloud.callFunction({
        name: 'mcloud',
        data: {
          route: 'checkin/submit', // 路由分发
          projectId: this.data.projectId,
          content: this.data.content,
          images: this.data.images // 云函数内会处理上传至云存储
        }
      });

      if (result.result.success) {
        wx.showToast({ title: '打卡成功', icon: 'success' });
        setTimeout(() => wx.navigateBack(), 1500);
      } else {
        wx.showToast({ title: result.result.msg, icon: 'none' });
      }
    } catch (err) {
      wx.showToast({ title: '网络错误', icon: 'none' });
    } finally {
      wx.hideLoading();
    }
  }
});

// 伪代码逻辑
async function exportSignupData(activityId) {
  // 1. 获取所有报名记录
  const list = await db.collection('signups')
    .where({ activity_id: activityId })
    .orderBy('signup_time', 'asc')
    .get();

  // 2. 数据格式化 (扁平化处理,方便Excel生成)
  const rows = list.data.map(item => ({
    姓名: item.form_data.name,
    手机: item.form_data.phone,
    报名时间: formatTime(item.signup_time),
    状态: transformStatus(item.status)
  }));

  // 3. 生成CSV或Excel文件流 (需引入第三方库如node-xlsx或在云函数生成临时文件)
  // 4. 返回文件临时下载链接
  return { fileUrl: tempFileUrl };
}

部署与测试说明

环境ID配置:必须分别在 cloudfunctions/mcloud/config/config.js 和 miniprogram/setting/setting.js 中填入相同的云环境ID,否则会导致“找不到CLOUD_ID”错误。 云函数依赖:上传云函数时必须选择“上传并部署:云端安装依赖”,以确保 wx-server-sdk 等库正确安装。 权限设置:在云开发控制台数据库设置中,建议将集合权限设置为“所有用户可读写”配合云函数校验,或“仅创建者可读写”,具体业务逻辑应在云函数中通过 OPENID 进行二次鉴权。

测试模块测试项预期结果
活动报名满员报名提示“人数已满”,数据库人数不增加
活动报名重复报名提示“您已报名”,不产生重复记录
现场签到非报名人员签到提示“未找到报名记录”
打卡功能同日多次打卡第二次提交被拦截,提示“今日已打卡”
后台管理导出数据成功下载包含所有报名者信息的Excel/CSV文件

UI设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

后台管理系统设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

git代码下载

点击下载