温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系名片!
一、项目背景与意义
众筹作为一种依托互联网的融资方式,近年来在公益救助、医疗互助、教育帮扶等领域得到广泛应用。传统的公益募捐往往依赖线下渠道,存在信息不透明、资金流向难以追踪、项目进度反馈滞后等问题。借助 Web 技术构建一套爱心众筹管理系统,可以把项目发起、审核、捐赠、资金公示、进度反馈等环节线上化,既降低了公益组织的运营成本,也提升了捐赠者的信任度。
本项目采用前后端分离架构,后端基于 Spring Boot 提供 RESTful 接口,前端使用 Vue3 构建单页应用,数据库选用 MySQL。系统面向普通捐赠者、项目发起人和平台管理员三类角色,覆盖众筹项目全生命周期管理,是一个典型且完整的 Java Web 综合实践项目。
二、系统需求分析
2.1 角色划分
- 普通用户(捐赠者):注册登录、浏览众筹项目、查看项目详情、在线捐赠、查看捐赠记录、收藏项目、发表留言评论。
- 项目发起人:发布众筹项目、上传项目图片与证明材料、查看捐赠明细、更新项目进度、申请提现。
- 平台管理员:用户管理、项目审核、分类管理、捐赠订单管理、资金流水管理、公告管理、数据统计。
2.2 核心功能模块
| 模块 | 主要功能 |
|---|---|
| 用户模块 | 注册、登录、JWT 鉴权、个人信息维护、实名认证 |
| 项目管理 | 项目发布、编辑、上下架、审核、分类检索、关键词搜索 |
| 捐赠模块 | 捐赠下单、模拟支付、订单查询、退款处理 |
| 进度模块 | 项目进度更新、图文反馈、资金使用公示 |
| 互动模块 | 留言评论、收藏、点赞 |
| 后台管理 | 数据看板、用户管理、审核管理、公告管理 |
三、技术选型
| 层次 | 技术栈 |
|---|---|
| 后端框架 | Spring Boot 2.7、Spring MVC、MyBatis-Plus |
| 安全认证 | Spring Security + JWT |
| 数据库 | MySQL 8.0、Redis(缓存与验证码) |
| 前端框架 | Vue3 + Vite + TypeScript |
| UI 组件 | Element Plus |
| 状态管理 | Pinia |
| 网络请求 | Axios |
| 文件存储 | 本地存储或阿里云 OSS |
四、系统架构设计
系统整体采用前后端分离的三层架构:前端 Vue3 单页应用负责页面渲染与交互,通过 Axios 调用后端接口;后端 Spring Boot 应用按 Controller、Service、Mapper 分层组织代码,统一返回 JSON 结果;MySQL 负责持久化存储,Redis 承担验证码、Token 黑名单和热点数据缓存。
flowchart TD
A[Vue3 前端] -->|Axios / HTTP| B[Spring Boot 后端]
B --> C[Controller 层]
C --> D[Service 层]
D --> E[Mapper 层]
E --> F[(MySQL)]
D --> G[(Redis)]
B --> H[文件存储 OSS]
五、数据库设计
核心数据表设计如下,字段可根据实际业务继续扩展。
sql
-- 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
nickname VARCHAR(50),
avatar VARCHAR(255),
phone VARCHAR(20),
role VARCHAR(20) DEFAULT 'USER',
status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 众筹项目表
CREATE TABLE crowdfunding_project (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
category_id BIGINT,
title VARCHAR(100) NOT NULL,
cover_img VARCHAR(255),
description TEXT,
target_amount DECIMAL(12,2) NOT NULL,
raised_amount DECIMAL(12,2) DEFAULT 0.00,
start_time DATETIME,
end_time DATETIME,
status TINYINT DEFAULT 0 COMMENT '0待审核 1进行中 2已结束 3已驳回',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 捐赠订单表
CREATE TABLE donation_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) NOT NULL UNIQUE,
project_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
message VARCHAR(255),
pay_status TINYINT DEFAULT 0 COMMENT '0未支付 1已支付 2已退款',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 项目进度表
CREATE TABLE project_progress (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
project_id BIGINT NOT NULL,
content TEXT,
images VARCHAR(1000),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
六、后端核心实现
6.1 项目依赖配置
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
6.2 统一响应结果封装
java
@Data
public class Result<T> implements Serializable {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMessage("操作成功");
r.setData(data);
return r;
}
public static <T> Result<T> error(String message) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMessage(message);
return r;
}
}
6.3 捐赠下单接口
java
@RestController
@RequestMapping("/api/donation")
@RequiredArgsConstructor
public class DonationController {
private final DonationService donationService;
@PostMapping("/create")
public Result<String> create(@RequestBody @Valid DonationDTO dto,
@RequestHeader("Authorization") String token) {
Long userId = JwtUtil.getUserId(token);
String orderNo = donationService.createOrder(userId, dto);
return Result.success(orderNo);
}
}
java
@Service
@RequiredArgsConstructor
public class DonationServiceImpl implements DonationService {
private final DonationOrderMapper orderMapper;
private final CrowdfundingProjectMapper projectMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrder(Long userId, DonationDTO dto) {
CrowdfundingProject project = projectMapper.selectById(dto.getProjectId());
if (project == null || project.getStatus() != 1) {
throw new BizException("项目不存在或已结束");
}
DonationOrder order = new DonationOrder();
order.setOrderNo(IdUtil.getSnowflakeNextIdStr());
order.setProjectId(dto.getProjectId());
order.setUserId(userId);
order.setAmount(dto.getAmount());
order.setMessage(dto.getMessage());
order.setPayStatus(0);
orderMapper.insert(order);
return order.getOrderNo();
}
}
6.4 JWT 登录鉴权
java
public class JwtUtil {
private static final String SECRET = "crowdfunding-secret-key";
private static final long EXPIRE = 7 * 24 * 60 * 60 * 1000L;
public static String createToken(Long userId, String username) {
return Jwts.builder()
.setSubject(username)
.claim("userId", userId)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public static Long getUserId(String token) {
token = token.replace("Bearer ", "");
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
return claims.get("userId", Long.class);
}
}
七、前端核心实现
7.1 Axios 请求封装
typescript
import axios from 'axios'
import { ElMessage } from 'element-plus'
const request = axios.create({
baseURL: '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
request.interceptors.response.use(
res => {
const { code, message, data } = res.data
if (code === 200) return data
ElMessage.error(message)
return Promise.reject(new Error(message))
},
err => {
ElMessage.error(err.message || '网络异常')
return Promise.reject(err)
}
)
export default request
7.2 项目列表页面
vue
<template>
<div class="project-list">
<el-row :gutter="20">
<el-col :span="6" v-for="item in projects" :key="item.id">
<el-card :body-style="{ padding: '0px' }" @click="goDetail(item.id)">
<img :src="item.coverImg" class="cover" />
<div class="info">
<h3>{{ item.title }}</h3>
<el-progress :percentage="calcPercent(item)" />
<p>已筹 ¥{{ item.raisedAmount }} / 目标 ¥{{ item.targetAmount }}</p>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import request from '@/utils/request'
const projects = ref<any[]>([])
const router = useRouter()
const loadProjects = async () => {
projects.value = await request.get('/project/list')
}
const calcPercent = (item: any) =>
Math.min(100, Math.floor((item.raisedAmount / item.targetAmount) * 100))
const goDetail = (id: number) => router.push(`/project/${id}`)
onMounted(loadProjects)
</script>
<style scoped>
.cover { width: 100%; height: 160px; object-fit: cover; }
.info { padding: 12px; }
</style>
八、系统运行与部署
- 创建 MySQL 数据库
crowdfunding,执行建表脚本。 - 修改后端
application.yml中的数据库连接、Redis 地址与文件存储配置。 - 后端执行
mvn spring-boot:run启动,默认端口 8080。 - 前端执行
npm install与npm run dev,通过 Vite 代理转发/api请求。 - 生产环境使用
npm run build打包,Nginx 托管静态资源并反向代理后端接口。










九、总结
本文围绕爱心众筹管理系统,完成了需求分析、技术选型、数据库设计以及前后端核心代码实现。系统采用 Spring Boot + Vue3 前后端分离架构,通过 JWT 实现无状态鉴权,借助 MyBatis-Plus 简化持久层开发,整体结构清晰、易于扩展。后续可在此基础上接入真实支付渠道、增加消息通知与数据可视化大屏,进一步提升系统的实用性与完整性。