本文目录导读:

这是一个基于 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请求频率
- 审计日志记录关键操作
这个案例提供了一个完整的志愿者平台基础框架,可以直接运行并作为实际项目的基础,你可以根据具体需求进行功能扩展和定制。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播