Spring Boot众筹平台前后台系统:从项目结构到可运行的最小闭环 简介这是一套基于SpringBoot开发的众筹平台前后台管理系统完整源码面向计算机相关专业学生与缺乏实战经验的初级开发者可用于课程设计、毕业设计参考或项目练习。系统涵盖前台用户注册登录、发起众筹、支持项目、个人中心以及后台项目审核、用户管理、订单管理等核心流程形成完整功能闭环。资源包共823个文件约27.33MB以167个Java源文件与167个JS脚本为主体辅以42个HTML页面、42个CSS样式及大量图片素材另有SQL脚本、XML配置与依赖jar包分层清晰、依赖精简便于快速运行与二次开发。技术栈采用JDK1.8、MySQL、SpringBoot、SpringSecurityOAuth2、Swagger2前端使用HTML、Vue、jQuery与Ajax。目前已有702人学习适合需要完整项目案例、排错思路与目录结构参考的读者。1. 众筹平台前后台系统从项目结构到可运行的最小闭环众筹平台听起来像是一个典型的“业务系统”但真正动手做的时候很多人会卡在一个很尴尬的位置后台管理页面能打开数据库表也建好了但前台项目列表、发起众筹、订单支付、后台审核这几条链路始终串不起来。尤其是当你拿到一份基于 Spring Boot 的众筹平台前后台管理系统源码时第一反应往往是“文件挺全”第二反应是“从哪开始跑”。这个标题对应的核心场景其实很明确一套用 Spring Boot 做后端、带前后台两套界面的众筹业务系统前台给普通用户浏览项目、发起众筹、下单支持后台给运营人员做项目审核、订单管理、用户管理、资金流水查看。它适合两类人一类是正在做课程设计或毕业设计需要一套能跑通、能讲清楚业务链路的参考实现另一类是想拿一个完整业务系统练手理解 Spring Boot 在真实项目里怎么做分层、怎么做权限、怎么做状态流转。我见过太多人把这类项目当成“下载即完成”结果卡在数据库连不上、前端请求跨域、后台登录 401、项目状态不流转这些具体问题上。这篇笔记不打算复述源码目录而是按“能跑起来、能改得动、能讲得清”的顺序把众筹平台前后台系统的落地路径拆开。你跟着走一遍至少能做到本地启动后端、初始化数据库、跑通前后台登录、走完一次众筹发起到后台审核的最小闭环。2. 众筹平台的技术选型与分层为什么用 Spring Boot 做前后台分离2.1 前后台分离下 Spring Boot 的职责边界众筹平台的前后台分离不是赶时髦而是业务本身决定的。前台面向普通用户需要频繁展示项目列表、项目详情、众筹进度、订单状态后台面向运营需要做审核、导出、状态变更、权限控制。两套界面的交互节奏和权限模型完全不同如果还用传统 JSP 混在一起写后期改一个审核按钮都可能影响前台页面。常见做法是后端只提供 REST 接口前台和后台各自独立部署或放在不同静态目录下。Spring Boot 在这里承担的是统一入口、统一鉴权、统一数据出口。具体来说Controller 层只负责接收参数和返回结果Service 层处理众筹业务规则Mapper 或 Repository 层负责数据库读写。这样做的直接好处是后台要加一个“批量审核”功能只需要新增一个接口不用动前台任何代码。我一般会把项目分成这几个包controller、service、mapper、entity、dto、vo、config、interceptor、util。其中 dto 用于接收前端参数vo 用于返回给前端展示entity 和数据库表一一对应。很多源码包会把 dto 和 vo 混在一起短期看不出问题等到项目状态字段需要在前台展示中文、后台展示英文枚举时就会到处改。2.2 数据库表设计众筹业务绕不开的六张核心表众筹平台的表不多但关系必须清楚。下面这张表是我在多个类似项目里总结出来的最小核心表集合少了任何一张业务链路都会断。表名作用关键字段user前台用户与后台管理员id, username, password, role, statusproject众筹项目主表id, user_id, title, target_amount, raised_amount, status, deadlineproject_detail项目详情与回报配置id, project_id, content, reward_desc, reward_amountorder支持订单id, user_id, project_id, amount, status, create_timepayment_flow资金流水id, order_id, amount, type, create_timeaudit_log后台审核记录id, project_id, admin_id, action, remark, create_time其中 project.status 是整个系统的状态机核心。常见状态包括待审核、审核通过、审核拒绝、众筹中、众筹成功、众筹失败。前台只能看到审核通过且处于众筹中的项目后台可以看到全部状态。order.status 则控制支付流程待支付、已支付、已退款、已关闭。注意很多源码把项目状态和订单状态写成纯数字没有枚举类。后期加一个“审核中”状态就要全局搜数字替换。建议一开始就定义 ProjectStatusEnum 和 OrderStatusEnum。2.3 用 Maven 搭出可运行的后端骨架拿到源码后不要急着改业务代码先把后端跑起来。下面是一个最小可运行的 pom.xml 依赖片段适用于 Spring Boot 2.7.x 和 JDK 8 或 11。版本号不要照抄按你本地环境调整但依赖组合是经过验证的。dependencies !-- Web 接口能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis 持久层众筹项目常用 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 分页插件项目列表和订单列表都要用 -- dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency !-- JWT 做前后台登录态 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency /dependencies这段依赖里spring-boot-starter-web 提供内嵌 Tomcat 和 MVC 能力mybatis-spring-boot-starter 负责把 Mapper 接口和 XML 映射文件关联起来pagehelper 用于项目列表分页众筹平台的项目列表几乎一定会分页jjwt 用于生成和解析 token前后台登录后返回 token后续请求放在 Header 里。参数说明MyBatis 的 mapper-locations 一般配置为 classpath:mapper/*.xmltype-aliases-package 配置为你的 entity 包路径。PageHelper 只需要在查询前调用 PageHelper.startPage(pageNum, pageSize)后面紧跟的查询就会自动分页。JWT 的 secret 不要写死在代码里放到 application.yml 中长度至少 32 位。2.4 前台与后台的接口隔离用拦截器区分两套登录态前后台共用一个后端时最容易翻车的地方是权限串了。前台用户登录后能调后台接口后台管理员登录后能改前台订单。常见做法是用两套拦截器按 URL 前缀区分。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { // 前台接口拦截器只校验普通用户 token registry.addInterceptor(new FrontAuthInterceptor()) .addPathPatterns(/api/front/**) .excludePathPatterns(/api/front/login, /api/front/register, /api/front/project/list); // 后台接口拦截器只校验管理员 token registry.addInterceptor(new AdminAuthInterceptor()) .addPathPatterns(/api/admin/**) .excludePathPatterns(/api/admin/login); } }逻辑说明前台接口统一走 /api/front/ 前缀后台接口统一走 /api/admin/ 前缀。两个拦截器分别解析 token 中的 role 字段前台只允许 roleuser后台只允许 roleadmin。排除路径是登录、注册和前台项目列表这些不需要登录就能访问。参数说明token 建议放在请求头 Authorization 中格式为 Bearer xxx。拦截器里解析失败直接返回 401不要抛异常到全局否则前端拿到的错误信息不明确。后台登录接口本身也要做频率限制防止暴力尝试简单做法是用 Redis 记录同一 IP 的失败次数。3. 从零跑通众筹核心链路发起、审核、支持、支付3.1 项目发起接口参数校验与状态初始化前台用户发起众筹是整条链路的起点。这个接口看起来简单但参数校验不严会导致后台审核时看到一堆脏数据。下面是一个典型的项目发起接口实现。PostMapping(/api/front/project/create) public Result createProject(RequestBody Valid ProjectCreateDTO dto, HttpServletRequest request) { // 从 token 中取当前用户 Long userId JwtUtil.getUserId(request); if (userId null) { return Result.fail(请先登录); } // 校验截止时间必须晚于当前时间 if (dto.getDeadline().before(new Date())) { return Result.fail(截止时间不能早于当前时间); } // 校验目标金额必须大于 0 if (dto.getTargetAmount() null || dto.getTargetAmount().compareTo(BigDecimal.ZERO) 0) { return Result.fail(目标金额必须大于 0); } Project project new Project(); project.setUserId(userId); project.setTitle(dto.getTitle()); project.setTargetAmount(dto.getTargetAmount()); project.setRaisedAmount(BigDecimal.ZERO); project.setStatus(ProjectStatusEnum.PENDING_AUDIT.getCode()); project.setDeadline(dto.getDeadline()); project.setCreateTime(new Date()); projectService.save(project); return Result.success(project.getId()); }逻辑说明这个接口做了三件事——取当前用户、校验关键参数、初始化项目状态。状态初始化为“待审核”而不是“众筹中”这是众筹平台和普通商品系统的核心区别。项目必须经过后台审核才能对外展示。参数说明ProjectCreateDTO 里 title 长度建议限制在 5 到 50 字targetAmount 用 BigDecimal 而不是 double避免金额精度问题。deadline 用 Date 或 LocalDateTime注意时区配置。raisedAmount 初始化为 0后续每次订单支付成功后累加。3.2 后台审核接口状态流转与审核日志后台审核是众筹平台的关键控制点。审核通过后项目才进入众筹中状态前台才能看到。审核拒绝后项目回到用户手里可以修改后重新提交。PostMapping(/api/admin/project/audit) public Result auditProject(RequestBody AuditDTO dto, HttpServletRequest request) { Long adminId JwtUtil.getUserId(request); Project project projectService.getById(dto.getProjectId()); if (project null) { return Result.fail(项目不存在); } // 只有待审核状态才能被审核 if (!ProjectStatusEnum.PENDING_AUDIT.getCode().equals(project.getStatus())) { return Result.fail(当前项目状态不允许审核); } if (dto.getPass()) { project.setStatus(ProjectStatusEnum.FUNDING.getCode()); } else { project.setStatus(ProjectStatusEnum.AUDIT_REJECT.getCode()); } projectService.update(project); // 写入审核日志方便追溯 AuditLog log new AuditLog(); log.setProjectId(project.getId()); log.setAdminId(adminId); log.setAction(dto.getPass() ? PASS : REJECT); log.setRemark(dto.getRemark()); log.setCreateTime(new Date()); auditLogService.save(log); return Result.success(); }逻辑说明审核接口先判断项目当前状态只有待审核状态才能被操作。审核通过后状态变为“众筹中”审核拒绝后状态变为“审核拒绝”。每次审核都写入 audit_log记录管理员、动作、备注和时间。参数说明AuditDTO 包含 projectId、pass、remark。pass 用 Boolean 而不是 int避免 0/1 歧义。remark 在拒绝时建议必填方便用户知道原因。审核日志表不要和项目表合并否则一个项目多次审核会覆盖历史记录。3.3 用户支持项目订单创建与支付回调用户支持项目本质是创建一个订单支付成功后累加项目已筹金额。这里最容易出问题的是并发两个人同时支持同一个项目raised_amount 可能少加。Transactional(rollbackFor Exception.class) public void paySuccess(Long orderId) { Order order orderMapper.selectById(orderId); if (order null || !OrderStatusEnum.UNPAID.getCode().equals(order.getStatus())) { return; } // 更新订单状态 order.setStatus(OrderStatusEnum.PAID.getCode()); order.setPayTime(new Date()); orderMapper.updateById(order); // 累加项目已筹金额使用 SQL 原子更新 projectMapper.increaseRaisedAmount(order.getProjectId(), order.getAmount()); // 写入资金流水 PaymentFlow flow new PaymentFlow(); flow.setOrderId(orderId); flow.setAmount(order.getAmount()); flow.setType(SUPPORT); flow.setCreateTime(new Date()); paymentFlowMapper.insert(flow); }逻辑说明支付回调里先判断订单状态防止重复回调。然后更新订单为已支付再用原子 SQL 累加项目已筹金额最后写资金流水。整个过程加 Transactional任何一步失败都回滚。参数说明increaseRaisedAmount 对应的 SQL 是 update project set raised_amount raised_amount #{amount} where id #{projectId}不要先查再改否则并发下会丢更新。资金流水的 type 字段区分支持、退款、提现等类型。3.4 众筹进度计算与前台展示前台项目列表和详情页都需要展示众筹进度。进度不是存出来的而是算出来的raised_amount / target_amount。但要注意目标金额为 0 的情况以及进度超过 100% 时的展示。public ProjectVO toVO(Project project) { ProjectVO vo new ProjectVO(); vo.setId(project.getId()); vo.setTitle(project.getTitle()); vo.setTargetAmount(project.getTargetAmount()); vo.setRaisedAmount(project.getRaisedAmount()); // 计算进度百分比保留两位小数 if (project.getTargetAmount().compareTo(BigDecimal.ZERO) 0) { BigDecimal percent project.getRaisedAmount() .multiply(new BigDecimal(100)) .divide(project.getTargetAmount(), 2, RoundingMode.HALF_UP); vo.setProgress(percent); } else { vo.setProgress(BigDecimal.ZERO); } // 计算剩余天数 long remainDays (project.getDeadline().getTime() - System.currentTimeMillis()) / (1000 * 60 * 60 * 24); vo.setRemainDays(Math.max(remainDays, 0)); return vo; }逻辑说明进度百分比用 BigDecimal 计算避免浮点误差。剩余天数用截止时间减当前时间小于 0 时展示 0。前台展示时进度条宽度直接用这个百分比。参数说明divide 的第二个参数是小数位数第三个参数是舍入模式。HALF_UP 是四舍五入符合展示习惯。如果项目已经众筹成功进度可能超过 100%前台展示时可以封顶 100%但数据库里保留真实值。4. 前后台联调避坑登录态、跨域、状态不一致4.1 登录态丢失token 存哪、怎么带、怎么续现象前台登录成功后刷新页面就退出登录或者后台能登录但点任何菜单都提示未登录。原因token 存储位置不统一。有的代码存在 localStorage有的存在 sessionStorage有的存在 cookie。前端请求拦截器没有统一加 Authorization 头后端拦截器又只认 Header。解决统一约定 token 存在 localStoragekey 为 token。前端封装 request 工具每次请求从 localStorage 取 token 放到 Header。后端拦截器只从 Header 取不兼容 cookie 和参数。token 过期时间建议前台 2 小时后台 30 分钟后台可以更短。4.2 跨域配置开发环境能跑部署后 404现象本地开发时前端 8080后端 8081请求正常。部署到服务器后前端请求全部 404 或 403。原因开发环境用了 CrossOrigin 注解或代理部署后没有配 Nginx 反向代理或者 Nginx 配置里没有转发 /api 前缀。解决开发环境用 Spring Boot 的 CorsConfig 统一放行生产环境用 Nginx 把 /api 转发到后端端口。不要同时用两种方式否则响应头里会出现两个 Access-Control-Allow-Origin浏览器直接拒绝。Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }参数说明addAllowedOriginPattern 用 * 而不是 addAllowedOrigin(*)后者在 allowCredentials 为 true 时会报错。生产环境建议把 * 换成具体域名。4.3 项目状态不一致前台看到已筹满后台还是众筹中现象项目已筹金额已经超过目标金额但前台还显示众筹中后台也没有自动变更状态。原因状态流转只靠人工操作没有定时任务或事件驱动。众筹成功或失败应该在截止时间到达时自动判定。解决加一个定时任务每天凌晨扫描截止时间已过且状态为众筹中的项目。如果 raised_amount target_amount状态改为众筹成功否则改为众筹失败。同时给发起人发站内信。Scheduled(cron 0 0 1 * * ?) public void checkProjectStatus() { ListProject list projectMapper.selectFundingExpired(); for (Project project : list) { if (project.getRaisedAmount().compareTo(project.getTargetAmount()) 0) { project.setStatus(ProjectStatusEnum.SUCCESS.getCode()); } else { project.setStatus(ProjectStatusEnum.FAIL.getCode()); } projectMapper.updateById(project); } }参数说明cron 表达式 0 0 1 * * ? 表示每天凌晨 1 点执行。selectFundingExpired 查询 status众筹中 且 deadline 当前时间 的项目。状态变更后前台项目详情页要能立刻看到所以不要缓存项目状态太久。4.4 订单重复支付回调幂等与前端防抖现象用户点击支付按钮后订单被重复回调raised_amount 加了两次。原因支付回调没有做幂等或者前端按钮没有防抖用户快速点了两次。解决后端在 paySuccess 里先判断订单状态只有待支付才处理。前端在点击支付后立即禁用按钮直到收到响应。另外支付平台回调可能重试所以幂等必须做在后端不能只靠前端。注意幂等判断和状态更新要在同一个事务里否则并发下两个回调可能同时通过状态判断。5. 后台管理进阶权限、导出与数据看板5.1 基于角色的菜单权限控制后台管理系统绕不开权限。众筹平台的后台一般分超级管理员、运营、财务三种角色。超级管理员看全部菜单运营看项目和用户财务看订单和流水。常见做法是角色表 菜单表 角色菜单关联表。登录后根据用户角色查询菜单列表前端动态渲染路由。后端接口也要做权限校验不能只靠前端隐藏菜单。GetMapping(/api/admin/menu/list) public ResultListMenuVO menuList(HttpServletRequest request) { Long adminId JwtUtil.getUserId(request); ListMenu menus menuService.selectByAdminId(adminId); return Result.success(menus.stream().map(this::toVO).collect(Collectors.toList())); }参数说明selectByAdminId 关联 admin_role 和 role_menu 两张表。菜单表里要有 path、component、icon、sort 字段前端根据这些字段生成路由。接口权限用注解 RequireRole(admin) 或拦截器里判断 URL 和角色的映射。5.2 订单与流水导出大数据量下的分页与流式写出后台经常需要导出订单和资金流水。数据量小的时候一次性查出来没问题数据量大了会内存溢出。常见做法是分页查询每页 1000 条用 POI 的 SXSSFWorkbook 流式写出。GetMapping(/api/admin/order/export) public void exportOrder(HttpServletResponse response) throws IOException { response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenameorders.xlsx); SXSSFWorkbook workbook new SXSSFWorkbook(100); // 保留100行在内存 Sheet sheet workbook.createSheet(订单); // 写表头 Row header sheet.createRow(0); header.createCell(0).setCellValue(订单号); header.createCell(1).setCellValue(项目名称); header.createCell(2).setCellValue(金额); // 分页查询并写入 int pageNum 1; while (true) { PageHelper.startPage(pageNum, 1000); ListOrder orders orderMapper.selectList(); if (orders.isEmpty()) break; for (int i 0; i orders.size(); i) { Row row sheet.createRow(sheet.getLastRowNum() 1); row.createCell(0).setCellValue(orders.get(i).getOrderNo()); row.createCell(1).setCellValue(orders.get(i).getProjectTitle()); row.createCell(2).setCellValue(orders.get(i).getAmount().doubleValue()); } pageNum; } workbook.write(response.getOutputStream()); workbook.dispose(); }逻辑说明SXSSFWorkbook 是 POI 的流式工作簿只保留指定行数在内存其余写入临时文件。分页查询每页 1000 条循环写入最后 dispose 清理临时文件。参数说明new SXSSFWorkbook(100) 中的 100 是内存中保留的行数太小会频繁写磁盘太大会占内存。分页大小 1000 是经验值可以根据服务器内存调整。导出接口要加超时时间避免大导出把 Tomcat 线程占满。5.3 数据看板用 SQL 聚合代替 Java 循环后台首页通常有一个数据看板展示今日新增项目、今日订单金额、累计用户数等。这些数据不要用 Java 查全量再循环统计直接用 SQL 聚合。-- 今日新增项目数 SELECT COUNT(*) FROM project WHERE DATE(create_time) CURDATE(); -- 今日订单总金额 SELECT IFNULL(SUM(amount), 0) FROM order WHERE status PAID AND DATE(pay_time) CURDATE(); -- 各状态项目占比 SELECT status, COUNT(*) AS num FROM project GROUP BY status;参数说明DATE(create_time) 会导致索引失效数据量大时建议用 create_time CURDATE() AND create_time CURDATE() INTERVAL 1 DAY。IFNULL 防止空结果返回 null。各状态占比可以在前端用饼图展示后端只返回状态和数量。5.4 审核日志追溯出问题时怎么查众筹平台涉及资金审核日志必须可追溯。每次后台审核、状态变更、退款操作都要写日志。日志表字段包括操作人、操作类型、目标 ID、变更前状态、变更后状态、备注、时间。查问题时先根据项目 ID 查 audit_log按时间倒序看最近一次状态变更是什么、谁操作的、备注写了什么。如果日志里没有记录说明代码里漏写了这是最常见的翻车点。我一般会在 Service 层的关键方法上加 AOP自动记录操作日志避免手动写漏。6. 把系统跑稳的几个具体习惯众筹平台这类系统功能跑通只是第一步真正花时间的是状态一致性和资金安全。我自己的习惯是任何涉及金额变更的操作先写流水再改状态流水是唯一不会骗人的东西。订单支付回调、退款、提现全部走同一个流水表出问题时按流水对账比查代码快得多。另一个习惯是状态机不要散落在各个 Service 里。项目状态、订单状态、审核状态全部用枚举类集中管理状态流转写在一个方法里任何地方要改状态都调这个方法。这样加一个“审核中”状态时只需要改枚举和流转方法不用全局搜数字。还有一点是接口幂等。支付回调、审核提交、订单创建这些接口都要考虑重复请求。简单做法是用唯一业务号 数据库唯一索引插入失败就返回已处理。不要依赖前端防抖前端防抖只能减少重复不能杜绝。最后本地跑通后一定要用 Postman 或 curl 把核心链路走一遍注册、登录、发起项目、后台审核、前台支持、支付回调、状态变更。每一步都看数据库里的数据对不对不要只看接口返回。接口返回成功但数据库没变的情况在事务配置错误时经常出现。希望帮到你。本文还有配套的精品资源点击获取