基于 Spring Boot + Vue3 的爱心众筹管理系统设计与实现

温馨提示:本人主页置顶文章(点我)开头有 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>

八、系统运行与部署

  1. 创建 MySQL 数据库 crowdfunding,执行建表脚本。
  2. 修改后端 application.yml 中的数据库连接、Redis 地址与文件存储配置。
  3. 后端执行 mvn spring-boot:run 启动,默认端口 8080。
  4. 前端执行 npm install 与 npm run dev,通过 Vite 代理转发 /api 请求。
  5. 生产环境使用 npm run build 打包,Nginx 托管静态资源并反向代理后端接口。

九、总结

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

相关推荐
沙蒿同学30 分钟前
Wails v3 实战:一个桌面提醒客户端,从 v2 升到 v3 的全过程
前端·后端·github
SimonKing36 分钟前
OpenCode免费模型直接给`Workbuddy`用,一键导入
java·后端·程序员
Tsuki_tl38 分钟前
【优选算法:双指针】——复写零题解
java·c++·算法·leetcode·数组·双指针·复写零
我命由我1234539 分钟前
Python 开发 - Python 项目部署(Windows 与 Linux)
java·linux·windows·后端·python·java-ee·python3.11
小羊没烦恼!44 分钟前
TypedSql:在 C# 类型系统上实现一个 SQL 查询引擎
java·linux·运维·服务器·开发语言·数据库·mysql
inquisiter1 小时前
KernelGpt的部分尝试
java·前端·apache
aibo8603081 小时前
基于 Spring Boot + Vue3 的餐饮食材采购与成本分析系统设计与实现
java·spring boot·后端
Geek-Chow1 小时前
Spring Boot: Integrating Swagger / OpenAPI With springdoc-openapi
java
Rain的Java大神之路1 小时前
面了上百人,我最爱问的LeetCode 1「两数之和」:能背代码的很多,能讲清“先查后存”的没几个
java·后端·算法