背景
当前志愿服务活动中,组织者与志愿者之间存在信息不对称、管理效率低下等痛点。传统的管理方式依赖人工统计和线下沟通,难以满足日益增长的志愿服务需求。
研究意义
- 对志愿者:提供及时的信息获取渠道,简化活动参与流程,清晰管理个人服务记录。
- 对组织方:实现活动管理的高效化,数据统计的精准化,以及服务质量的可量化。
系统需求分析

业务流程设计
- 预约流程:用户浏览活动 -> 选择时段 -> 提交预约 -> 生成核销码。
- 核销流程:用户出示二维码 -> 管理员扫码 -> 系统校验 -> 更新状态为“已完成”。
- 管理流程:管理员登录 -> 发布活动/设置规则 -> 监控报名情况 -> 现场核销 -> 导出数据。
关键代码片段
package com.volunteer.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_appointment")
public class Appointment {
@TableId(type = IdType.AUTO)
package com.volunteer.service.impl;
import com.volunteer.entity.Activity;
import com.volunteer.entity.Appointment;
import com.volunteer.mapper.ActivityMapper;
import com.volunteer.mapper.AppointmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class AppointmentServiceImpl {
@Autowired
private ActivityMapper activityMapper;
@Autowired
private AppointmentMapper appointmentMapper;
@Transactional(rollbackFor = Exception.class)
public boolean bookActivity(Long userId, Long activityId, String timeSlot) {
Activity activity = activityMapper.selectById(activityId);
if (activity == null || activity.getStatus() != 1) {
throw new RuntimeException("活动不存在或未开启");
}
if (activity.getBookedCount() >= activity.getTotalQuota()) {
throw new RuntimeException("名额已满");
}
long count = appointmentMapper.selectCount(
new LambdaQueryWrapper<Appointment>()
.eq(Appointment::getUserId, userId)
.eq(Appointment::getActivityId, activityId)
.eq(Appointment::getTimeSlot, timeSlot)
.in(Appointment::getStatus, 0, 1)
);
if (count > 0) {
throw new RuntimeException("您已预约该时段");
}
int updatedRows = activityMapper.decrementQuota(activityId);
if (updatedRows == 0) {
throw new RuntimeException("预约失败,名额已被抢光");
}
Appointment appointment = new Appointment();
appointment.setUserId(userId);
appointment.setActivityId(activityId);
appointment.setTimeSlot(timeSlot);
appointment.setVerifyCode(UUID.randomUUID().toString().replace("-", ""));
appointment.setStatus(0);
appointment.setBookTime(LocalDateTime.now());
appointmentMapper.insert(appointment);
return true;
}
}
数据库设计
| 字段名 | 类型 | 长度 | 主键 | 非空 | 默认值 | 说明 |
|---|
user_id | BIGINT | 20 | YES | YES | AUTO_INC | 用户ID |
openid | VARCHAR | 64 | NO | YES | - | 微信OpenID (唯一标识) |
name | VARCHAR | 50 | NO | YES | - | 真实姓名 |
phone | VARCHAR | 20 | NO | NO | - | 联系电话 |
specialty | VARCHAR | 255 | NO | NO | - | 服务特长/技能 |
avatar_url | VARCHAR | 255 | NO | NO | - | 头像链接 |
created_at | DATETIME | - | NO | YES | NOW() | 注册时间 |
updated_at | DATETIME | - | NO | YES | NOW() | 更新时间 |
| 字段名 | 类型 | 长度 | 主键 | 非空 | 默认值 | 说明 |
|---|
activity_id | BIGINT | 20 | YES | YES | AUTO_INC | 活动ID |
title | VARCHAR | 100 | NO | YES | - | 活动标题 |
type | VARCHAR | 50 | NO | YES | - | 活动类型 (如: 环保, 助老) |
description | TEXT | - | NO | NO | - | 活动详情描述 |
location | VARCHAR | 255 | NO | NO | - | 活动地点 |
total_quota | INT | 11 | NO | YES | 0 | 总招募人数 |
booked_count | INT | 11 | NO | YES | 0 | 已报名人数 |
start_time | DATETIME | - | NO | YES | - | 活动开始时间 |
end_time | DATETIME | - | NO | YES | - | 活动结束时间 |
status | TINYINT | 1 | NO | YES | 1 | 状态 (0:下架, 1:进行中, 2:已结束) |
created_by | BIGINT | 20 | NO | YES | - | 创建者ID (Admin) |
created_at | DATETIME | - | NO | YES | NOW() | 发布时间 |
| 字段名 | 类型 | 长度 | 主键 | 非空 | 默认值 | 说明 |
|---|
content_id | BIGINT | 20 | YES | YES | AUTO_INC | 内容ID |
category | VARCHAR | 50 | NO | YES | - | 分类 (notice, knowledge, etc.) |
title | VARCHAR | 100 | NO | YES | - | 标题 |
body | LONGTEXT | - | NO | NO | - | 正文内容 (HTML/Markdown) |
cover_img | VARCHAR | 255 | NO | NO | - | 封面图URL |
view_count | INT | 11 | NO | YES | 0 | 浏览次数 |
created_at | DATETIME | - | NO | YES | NOW() | 发布时间 |
| 字段名 | 类型 | 长度 | 主键 | 非空 | 默认值 | 说明 |
|---|
admin_id | BIGINT | 20 | YES | YES | AUTO_INC | 管理员ID |
username | VARCHAR | 50 | NO | YES | - | 登录用户名 |
password_hash | VARCHAR | 100 | NO | YES | - | 加密后的密码 |
role | VARCHAR | 20 | NO | YES | 'admin' | 角色 (super_admin, admin, checker) |
is_active | TINYINT | 1 | NO | YES | 1 | 账户状态 (0:禁用, 1:启用) |
last_login | DATETIME | - | NO | NO | - | 最后登录时间 |
关键代码逻辑
- 并发控制:在预约提交时利用数据库锁 (SELECT ... FOR UPDATE) 或 Redis 缓存防止超卖(名额超出)。
- 权限拦截:后端通过 Interceptor 拦截器验证管理员 Token 及角色权限。
- 入口设计:在小程序“我的->设置”中嵌入管理入口,实现移动端管理。
- 数据可视化:通过图表或列表展示预约统计、用户增长等数据。
- Excel导出:后端使用POI或EasyExcel库,将查询结果封装为Excel流返回前端下载。
UI设计

后台管理系统设计

测试结果
- 功能测试:各模块功能(注册、预约、核销、导出)均运行正常。
- 性能测试:在高并发预约场景下,系统响应稳定,无数据不一致现象。
- 兼容性测试:在不同型号手机及微信版本上界面显示正常。
git下载
点击git下载