ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序日程管理系统开发全流程解析

微信小程序日程管理系统开发全流程解析

1. 项目背景与核心需求

作为一名长期被日程管理问题困扰的职场人,我深刻体会到纸质记事本和手机自带日历的局限性。去年团队协作时,因为错过一个重要会议节点,直接导致项目延期,这件事促使我下定决心开发一个专属的日程管理工具。经过三个月的开发迭代,这个基于微信小程序的个人日程管理系统终于成型,现在把完整开发过程分享给大家。

这个系统的核心解决三个痛点:

  1. 多场景日程整合:将工作会议、私人约会、纪念日等分散记录在不同平台的内容统一管理
  2. 智能提醒体系:通过倒数日计算和微信服务通知,避免重要事项遗漏
  3. 轻量化协同:支持日程共享功能,满足简单的团队协作需求

技术选型上,前端采用微信小程序保证使用便捷性,后端使用SSM框架搭建RESTful API,数据库选用MySQL 8.0。这种组合既保证了开发效率,又能应对初期用户量增长。

2. 系统架构设计

2.1 技术栈全景图

整个系统采用典型的三层架构:

微信小程序端(表现层) ↓ Java后端服务(业务逻辑层) ↓ MySQL数据库(数据持久层)
2.1.1 前端技术方案
  • 小程序基础框架:使用微信原生WXML+WXSS+JS开发,放弃uniapp等跨平台方案,确保最佳性能
  • 组件库选择:采用Vant Weapp组件库,特别其日历组件二次开发后作为核心展示界面
  • 状态管理:对于跨页面共享的日程数据,使用小程序自带的globalData机制
2.1.2 后端技术方案
  • 框架组合:Spring 5.2 + Spring MVC + MyBatis 3.5(SSM)
  • 安全控制:JWT令牌实现接口鉴权,配合微信登录的openid作为用户唯一标识
  • API设计:遵循RESTful规范,所有接口返回统一JSON格式:
{ "code": 200, "data": {}, "message": "success" }
2.1.3 数据库设计

选择MySQL 8.0主要考虑:

  1. 事务完整性要求不高,不需要Oracle级别的特性
  2. JSON字段支持良好,便于存储日程的扩展属性
  3. 开发环境可免费使用,云数据库成本可控

2.2 核心功能模块

系统功能划分为六大模块,通过UML用例图展示主要功能点:

用户管理 ├─ 微信一键登录 ├─ 个人信息维护 日程管理 ├─ 日程CRUD ├─ 日历视图展示 ├─ 智能提醒设置 工作日程 ├─ 会议管理 ├─ 任务分解 ├─ 进度追踪 重要日管理 ├─ 纪念日记录 ├─ 倒数日计算 ├─ 循环事件设置 数据统计 ├─ 日程完成率 ├─ 时间分布热力图 系统管理 ├─ 用户管理 ├─ 数据备份

3. 数据库详细设计

3.1 表结构优化实践

原始设计中有较多冗余字段,经过第三范式优化后,核心表减少到7张:

3.1.1 用户表(sys_user)
CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `openid` varchar(32) NOT NULL COMMENT '微信openid', `nickname` varchar(50) DEFAULT NULL, `avatar` varchar(255) DEFAULT NULL, `mobile` varchar(20) DEFAULT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.1.2 日程主表(schedule)
CREATE TABLE `schedule` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `title` varchar(100) NOT NULL, `content` text, `schedule_type` tinyint NOT NULL COMMENT '1-工作 2-生活 3-纪念日', `start_time` datetime NOT NULL, `end_time` datetime DEFAULT NULL, `location` varchar(255) DEFAULT NULL, `remind_type` tinyint DEFAULT '1' COMMENT '1-不提醒 2-开始时 3-提前5分钟', `repeat_mode` varchar(20) DEFAULT NULL COMMENT 'daily/weekly/monthly', `status` tinyint DEFAULT '0' COMMENT '0-未开始 1-已完成', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_time` (`user_id`,`start_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.1.3 共享关系表(schedule_share)
CREATE TABLE `schedule_share` ( `id` bigint NOT NULL AUTO_INCREMENT, `schedule_id` bigint NOT NULL, `from_user` bigint NOT NULL, `to_user` bigint NOT NULL, `permission` tinyint DEFAULT '1' COMMENT '1-查看 2-编辑', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_share` (`schedule_id`,`to_user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.2 索引优化方案

针对查询场景建立复合索引:

  1. 用户日程查询:(user_id, start_time)
  2. 共享日程查询:(to_user, schedule_id)
  3. 时间范围查询:(schedule_type, start_time)

注意:避免在频繁更新的字段上建立索引,如status字段。实际测试发现,对status建索引反而使写入性能下降30%

4. 后端核心实现

4.1 微信登录流程

采用官方推荐的code换session方案:

小程序端获取code → 后端用code+appid+secret请求微信接口 → 获取session_key和openid → 生成自定义登录态 → 返回token给小程序

关键代码实现:

@RestController @RequestMapping("/auth") public class AuthController { @Value("${wx.appid}") private String appId; @Value("${wx.secret}") private String secret; @PostMapping("/login") public Result login(@RequestParam String code) { // 构造请求URL String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appId + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code"; // 发送HTTP请求 String response = restTemplate.getForObject(url, String.class); JSONObject json = JSON.parseObject(response); // 处理异常情况 if(json.containsKey("errcode")) { return Result.error(json.getIntValue("errcode"), json.getString("errmsg")); } // 获取openid和session_key String openid = json.getString("openid"); String sessionKey = json.getString("session_key"); // 查询或创建用户 User user = userService.getOrCreate(openid); // 生成JWT令牌 String token = JwtUtil.generateToken(user.getId()); return Result.success(token); } }

4.2 日程提醒实现

采用定时任务+微信模板消息方案:

  1. 使用Spring Scheduled每5分钟扫描即将开始的日程
  2. 通过微信订阅消息发送提醒

定时任务配置:

@Configuration @EnableScheduling public class ScheduleConfig { @Autowired private RemindService remindService; @Scheduled(cron = "0 */5 * * * ?") public void checkReminders() { // 查询5分钟内开始的日程 List<Schedule> schedules = scheduleMapper.selectNeedRemind( LocalDateTime.now(), LocalDateTime.now().plusMinutes(5) ); // 发送微信提醒 schedules.forEach(schedule -> { remindService.sendWechatRemind(schedule); }); } }

微信模板消息发送:

public void sendWechatRemind(Schedule schedule) { // 获取access_token String accessToken = wechatService.getAccessToken(); // 构造请求数据 JSONObject data = new JSONObject(); data.put("touser", schedule.getUser().getOpenid()); data.put("template_id", "TEMPLATE_ID"); data.put("page", "/pages/detail?id=" + schedule.getId()); JSONObject content = new JSONObject(); content.put("thing1", new JSONObject().put("value", schedule.getTitle())); content.put("time2", new JSONObject().put("value", schedule.getStartTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))); data.put("data", content); // 发送请求 restTemplate.postForObject( "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken, data, String.class ); }

5. 小程序前端开发

5.1 核心页面实现

5.1.1 日历视图页

采用自定义组件实现月视图+周视图切换:

<view class="calendar-container"> <view class="header"> <view class="arrow" bindtap="prevMonth">◀</view> <view class="title">{{year}}年{{month}}月</view> <view class="arrow" bindtap="nextMonth">▶</view> </view> <view class="weekdays"> <view wx:for="{{['日','一','二','三','四','五','六']}}" wx:key="*this">{{item}}</view> </view> <view class="days"> <view wx:for="{{days}}" wx:key="date" class="day {{item.isToday ? 'today' : ''}} {{item.isCurrentMonth ? '' : 'other-month'}}" bindtap="selectDay" >Page({ data: { schedule: null, loading: true }, onLoad(options) { const id = options.id; wx.request({ url: `https://api.yourdomain.com/schedules/${id}`, header: { 'Authorization': `Bearer ${getApp().globalData.token}` }, success: (res) => { if(res.data.code === 200) { this.setData({ schedule: res.data.data, loading: false }); } } }); }, handleEdit() { wx.navigateTo({ url: `/pages/edit/index?id=${this.data.schedule.id}` }); } });

5.2 性能优化实践

  1. 图片懒加载:对日程封面图使用lazy-load属性
  2. 数据分页:日程列表实现上拉加载更多
  3. 缓存策略:对静态数据使用wx.setStorage缓存
  4. 请求合并:同页面多个接口合并为批量请求
// 分页加载示例 let currentPage = 1; function loadMore() { wx.showLoading({title: '加载中'}); wx.request({ url: 'https://api.yourdomain.com/schedules', data: { page: currentPage, size: 10 }, success: (res) => { if(res.data.data.length > 0) { this.setData({ list: [...this.data.list, ...res.data.data] }); currentPage++; } else { wx.showToast({title: '没有更多了', icon: 'none'}); } }, complete: () => { wx.hideLoading(); } }); }

6. 部署与运维

6.1 服务器环境

推荐最低配置:

  • 腾讯云轻量应用服务器
  • 2核CPU / 4GB内存
  • CentOS 7.6
  • MySQL 8.0
  • JDK 11

6.2 部署流程

  1. 后端部署:
# 打包 mvn clean package -DskipTests # 上传jar包 scp target/schedule-app.jar user@server:/app/ # 启动服务 nohup java -jar -Dspring.profiles.active=prod /app/schedule-app.jar > /app/logs/app.log 2>&1 &
  1. 小程序部署:
  • 开发版本 → 体验版 → 提交审核 → 发布线上

6.3 监控方案

基础监控配置:

  • Spring Boot Actuator暴露健康检查接口
  • Prometheus + Grafana监控JVM指标
  • 微信小程序错误日志接入Sentry

7. 踩坑经验总结

  1. 微信登录session_key泄露问题

    • 错误做法:将session_key传到前端
    • 正确方案:session_key只保存在服务端,通过token维持会话
  2. MySQL时区问题

    • 现象:存储的时间比实际少8小时
    • 解决:jdbc连接串添加serverTimezone=Asia/Shanghai
  3. 小程序图片上传限制

    • 单个文件不能超过10MB
    • 解决方案:超过2MB的图片先压缩再上传
  4. 订阅消息发送失败

    • 常见原因:用户未授权或模板ID错误
    • 调试技巧:先在小程序端调用wx.requestSubscribeMessage测试
  5. 日历组件性能优化

    • 初始方案:全量渲染当月数据导致卡顿
    • 优化后:按需加载可见区域数据

这个项目从设计到上线共迭代了4个版本,核心体会是:对于工具类小程序,一定要克制功能膨胀的冲动,把核心体验做到极致。目前系统日活稳定在500+,后续计划增加Web端和桌面提醒功能。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进