本文目录导读:

这是一个基于 Spring Boot + Thymeleaf + MySQL 的志愿者平台案例,该案例模拟了志愿者注册、查看活动、报名活动以及后台管理的基本流程。
1 技术栈
- 后端: Spring Boot 2.x, Spring Data JPA, Spring Security
- 前端: Thymeleaf, Bootstrap 5, HTML5, CSS3
- 数据库: MySQL 8.0
- 构建工具: Maven
2 功能模块
| 模块名称 | 功能描述 |
|---|---|
| 用户模块 | 志愿者注册、登录、个人信息管理、服务时长统计 |
| 活动模块 | 查看活动列表、活动详情、报名/取消报名 |
| 组织模块 | 查看组织信息、申请加入组织 |
| 后台管理 | 活动管理(CRUD)、用户管理、数据统计 |
| 通知模块 | 活动提醒、报名确认通知 |
数据库设计
SQL建表语句
-- 用户表
CREATE TABLE `volunteer_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL UNIQUE,
`password` varchar(100) NOT NULL,
`real_name` varchar(50),
`phone` varchar(20),
`email` varchar(100),
`avatar` varchar(255),
`role` enum('ADMIN','VOLUNTEER','ORGANIZATION') DEFAULT 'VOLUNTEER',
`total_hours` decimal(10,2) DEFAULT 0.00,
`status` tinyint DEFAULT 1,
`created_at` datetime,
`updated_at` datetime,
PRIMARY KEY (`id`)
);
-- 活动表
CREATE TABLE `activity` (
`id` bigint NOT NULL AUTO_INCREMENT, varchar(200) NOT NULL,
`description` text,
`location` varchar(255),
`start_time` datetime,
`end_time` datetime,
`max_volunteers` int DEFAULT 0,
`registered_volunteers` int DEFAULT 0,
`status` enum('PENDING','ACTIVE','COMPLETED','CANCELLED') DEFAULT 'PENDING',
`category` varchar(50),
`hours_awarded` decimal(10,2) DEFAULT 0.00,
`organization_id` bigint,
`created_at` datetime,
`updated_at` datetime,
PRIMARY KEY (`id`),
FOREIGN KEY (`organization_id`) REFERENCES `volunteer_user`(`id`)
);
-- 报名表
CREATE TABLE `registration` (
`id` bigint NOT NULL AUTO_INCREMENT,
`volunteer_id` bigint NOT NULL,
`activity_id` bigint NOT NULL,
`status` enum('REGISTERED','CONFIRMED','COMPLETED','CANCELLED') DEFAULT 'REGISTERED',
`registration_time` datetime,
`checkin_time` datetime,
`checkout_time` datetime,
`actual_hours` decimal(10,2),
`created_at` datetime,
`updated_at` datetime,
PRIMARY KEY (`id`),
FOREIGN KEY (`volunteer_id`) REFERENCES `volunteer_user`(`id`),
FOREIGN KEY (`activity_id`) REFERENCES `activity`(`id`),
UNIQUE KEY `uk_vol_act` (`volunteer_id`, `activity_id`)
);
核心代码实现
1 项目结构
src/main/java/com/volunteer
├── config
│ ├── SecurityConfig.java # Spring Security配置
│ └── WebMvcConfig.java # MVC配置
├── controller
│ ├── HomeController.java # 首页控制器
│ ├── ActivityController.java # 活动控制器
│ ├── UserController.java # 用户控制器
│ └── AdminController.java # 管理员控制器
├── entity
│ ├── User.java
│ ├── Activity.java
│ └── Registration.java
├── repository
│ ├── UserRepository.java
│ ├── ActivityRepository.java
│ └── RegistrationRepository.java
├── service
│ ├── UserService.java
│ ├── ActivityService.java
│ └── RegistrationService.java
├── dto
│ └── ActivityDTO.java
└── VolunteerApplication.java
2 核心实体类
User.java
@Entity
@Table(name = "volunteer_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String realName;
private String phone;
private String email;
private String avatar;
@Enumerated(EnumType.STRING)
private UserRole role = UserRole.VOLUNTEER;
@Column(precision = 10, scale = 2)
private BigDecimal totalHours = BigDecimal.ZERO;
private Integer status = 1;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
enum UserRole {
ADMIN, VOLUNTEER, ORGANIZATION
}
3 服务层关键实现
ActivityService.java (活动报名核心逻辑)
@Service
@Transactional
public class ActivityService {
@Autowired
private ActivityRepository activityRepository;
@Autowired
private RegistrationRepository registrationRepository;
public String registerForActivity(Long volunteerId, Long activityId) {
// 1. 检查活动是否存在
Activity activity = activityRepository.findById(activityId)
.orElseThrow(() -> new RuntimeException("活动不存在"));
// 2. 检查活动状态
if (activity.getStatus() != ActivityStatus.ACTIVE) {
return "活动未开放报名";
}
// 3. 检查是否已报名
if (registrationRepository.existsByVolunteerIdAndActivityId(volunteerId, activityId)) {
return "您已报名此活动";
}
// 4. 检查名额是否已满
if (activity.getRegisteredVolunteers() >= activity.getMaxVolunteers()) {
return "活动名额已满";
}
// 5. 创建报名记录
Registration registration = new Registration();
registration.setVolunteerId(volunteerId);
registration.setActivityId(activityId);
registration.setStatus(RegistrationStatus.REGISTERED);
registration.setRegistrationTime(LocalDateTime.now());
registrationRepository.save(registration);
// 6. 更新活动已报名人数
activity.setRegisteredVolunteers(activity.getRegisteredVolunteers() + 1);
activityRepository.save(activity);
return "报名成功";
}
public String cancelRegistration(Long volunteerId, Long activityId) {
// 取消报名逻辑,释放名额
Registration registration = registrationRepository
.findByVolunteerIdAndActivityId(volunteerId, activityId)
.orElseThrow(() -> new RuntimeException("报名记录不存在"));
if (registration.getStatus() != RegistrationStatus.REGISTERED) {
return "无法取消,报名状态已改变";
}
registration.setStatus(RegistrationStatus.CANCELLED);
registrationRepository.save(registration);
Activity activity = activityRepository.findById(activityId).get();
activity.setRegisteredVolunteers(activity.getRegisteredVolunteers() - 1);
activityRepository.save(activity);
return "取消成功";
}
}
4 配置类
SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/register", "/login", "/css/**", "/js/**", "/images/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/activity/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
5 前端页面示例
活动列表页面 (activity-list.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">志愿活动列表</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>志愿活动列表</h2>
<div class="row mt-4">
<div class="col-md-4 mb-4" th:each="activity : ${activities}">
<div class="card">
<div class="card-body">
<h5 class="card-title" th:text="${activity.title}">活动标题</h5>
<p class="card-text" th:text="${activity.description}">活动描述</p>
<p class="card-text">
<small class="text-muted">
📍 <span th:text="${activity.location}">地点</span><br>
🕐 <span th:text="${#temporals.format(activity.startTime, 'yyyy-MM-dd HH:mm')}">开始时间</span><br>
👥 已报名: <span th:text="${activity.registeredVolunteers}">0</span> / <span th:text="${activity.maxVolunteers}">50</span>
</small>
</p>
<div th:if="${activity.status.name() == 'ACTIVE'}">
<button class="btn btn-primary"
th:onclick="'registerForActivity(' + ${activity.id} + ')'
th:disabled="${activity.registeredVolunteers >= activity.maxVolunteers}">
立即报名
</button>
</div>
<div th:unless="${activity.status.name() == 'ACTIVE'}" class="text-muted">
<span th:if="${activity.status.name() == 'PENDING'}">即将开始</span>
<span th:if="${activity.status.name() == 'COMPLETED'}">已结束</span>
<span th:if="${activity.status.name() == 'CANCELLED'}">已取消</span>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:inline="javascript">
function registerForActivity(activityId) {
fetch(`/activity/register/${activityId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.text())
.then(data => {
alert(data);
location.reload();
});
}
</script>
</body>
</html>
运行与测试
1 配置文件
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/volunteer_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
server:
port: 8080
2 启动步骤
-
创建数据库
CREATE DATABASE volunteer_db;
-
修改配置 修改
application.yml中的数据库连接信息 -
运行项目
mvn spring-boot:run
-
访问系统
- 首页: http://localhost:8080
- 默认管理员账号: admin / admin123
扩展与优化建议
1 功能扩展
- 积分系统: 根据服务时长和活动评价给予积分
- 地图服务: 集成百度/高德地图,显示活动位置
- 即时通讯: 使用WebSocket实现志愿者即时通讯
- 智能推荐: 基于志愿者兴趣推荐活动
2 性能优化
- 使用Redis缓存热门活动数据
- 异步处理报名通知邮件/短信
- 数据库读写分离
3 安全性增强
- 增加验证码功能防止恶意注册
- 限制单个IP请求频率
- 审计日志记录关键操作
这个案例提供了一个完整的志愿者平台基础框架,可以直接运行并作为实际项目的基础,你可以根据具体需求进行功能扩展和定制。