从10到100:基于java的志愿者小程序开发笔记

0 阅读4分钟

背景

当前志愿服务活动中,组织者与志愿者之间存在信息不对称、管理效率低下等痛点。传统的管理方式依赖人工统计和线下沟通,难以满足日益增长的志愿服务需求。

研究意义

  • 对志愿者:提供及时的信息获取渠道,简化活动参与流程,清晰管理个人服务记录。
  • 对组织方:实现活动管理的高效化,数据统计的精准化,以及服务质量的可量化。

系统需求分析

在这里插入图片描述

业务流程设计

  • 预约流程:用户浏览活动 -> 选择时段 -> 提交预约 -> 生成核销码。
  • 核销流程:用户出示二维码 -> 管理员扫码 -> 系统校验 -> 更新状态为“已完成”。
  • 管理流程:管理员登录 -> 发布活动/设置规则 -> 监控报名情况 -> 现场核销 -> 导出数据。

关键代码片段

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;

    /**
     * 用户预约活动
     * @param userId 用户ID
     * @param activityId 活动ID
     * @param timeSlot 时段
     * @return 预约结果
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean bookActivity(Long userId, Long activityId, String timeSlot) {
        // 1. 查询活动信息 (使用 selectById 并锁定行,或使用版本号机制)
        // 这里演示简单的检查逻辑,生产环境建议结合 Redis 预减库存
        Activity activity = activityMapper.selectById(activityId);
        
        if (activity == null || activity.getStatus() != 1) {
            throw new RuntimeException("活动不存在或未开启");
        }
        
        if (activity.getBookedCount() >= activity.getTotalQuota()) {
            throw new RuntimeException("名额已满");
        }

        // 2. 检查用户是否重复预约该时段
        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("您已预约该时段");
        }

        // 3. 扣减库存 (利用数据库原子更新,防止并发超卖)
        // SQL: UPDATE t_activity SET booked_count = booked_count + 1 
        //      WHERE activity_id = ? AND booked_count < total_quota
        int updatedRows = activityMapper.decrementQuota(activityId);
        if (updatedRows == 0) {
            throw new RuntimeException("预约失败,名额已被抢光");
        }

        // 4. 创建预约记录
        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_idBIGINT20YESYESAUTO_INC用户ID
openidVARCHAR64NOYES-微信OpenID (唯一标识)
nameVARCHAR50NOYES-真实姓名
phoneVARCHAR20NONO-联系电话
specialtyVARCHAR255NONO-服务特长/技能
avatar_urlVARCHAR255NONO-头像链接
created_atDATETIME-NOYESNOW()注册时间
updated_atDATETIME-NOYESNOW()更新时间
字段名类型长度主键非空默认值说明
activity_idBIGINT20YESYESAUTO_INC活动ID
titleVARCHAR100NOYES-活动标题
typeVARCHAR50NOYES-活动类型 (如: 环保, 助老)
descriptionTEXT-NONO-活动详情描述
locationVARCHAR255NONO-活动地点
total_quotaINT11NOYES0总招募人数
booked_countINT11NOYES0已报名人数
start_timeDATETIME-NOYES-活动开始时间
end_timeDATETIME-NOYES-活动结束时间
statusTINYINT1NOYES1状态 (0:下架, 1:进行中, 2:已结束)
created_byBIGINT20NOYES-创建者ID (Admin)
created_atDATETIME-NOYESNOW()发布时间
字段名类型长度主键非空默认值说明
content_idBIGINT20YESYESAUTO_INC内容ID
categoryVARCHAR50NOYES-分类 (notice, knowledge, etc.)
titleVARCHAR100NOYES-标题
bodyLONGTEXT-NONO-正文内容 (HTML/Markdown)
cover_imgVARCHAR255NONO-封面图URL
view_countINT11NOYES0浏览次数
created_atDATETIME-NOYESNOW()发布时间
字段名类型长度主键非空默认值说明
admin_idBIGINT20YESYESAUTO_INC管理员ID
usernameVARCHAR50NOYES-登录用户名
password_hashVARCHAR100NOYES-加密后的密码
roleVARCHAR20NOYES'admin'角色 (super_admin, admin, checker)
is_activeTINYINT1NOYES1账户状态 (0:禁用, 1:启用)
last_loginDATETIME-NONO-最后登录时间

关键代码逻辑

  • 并发控制:在预约提交时利用数据库锁 (SELECT ... FOR UPDATE) 或 Redis 缓存防止超卖(名额超出)。
  • 权限拦截:后端通过 Interceptor 拦截器验证管理员 Token 及角色权限。
  • 入口设计:在小程序“我的->设置”中嵌入管理入口,实现移动端管理。
  • 数据可视化:通过图表或列表展示预约统计、用户增长等数据。
  • Excel导出:后端使用POI或EasyExcel库,将查询结果封装为Excel流返回前端下载。

UI设计

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

后台管理系统设计

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

测试结果

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

git下载

点击git下载