简介:这是基于React与Spring Boot的前后端分离校园社交平台项目,面向Java后端或前端学习者,提供从零搭建完整业务系统的参考,适合课程设计、毕业设计或项目实战练手。功能上实现用户注册登录、动态发布与点赞、个人资料维护;管理员端支持用户管理、帖子管理及审核,涵盖增删改查、通过/拒绝等操作,覆盖校园社交的主要场景。代码按前端与后端模块划分,前端展示React组件化开发、路由跳转与请求封装,后端体现Spring Boot分层思想、RESTful接口及权限控制,可借此理解跨域处理、前后端联调等真实工程细节,同时提供了接口调用示例,便于梳理数据流转。资源以zip压缩包形式提供,大小约1.39MB,目前已有112人学习下载,适合希望巩固全栈开发能力的中初级开发者参考。
1. 拿到“校园社交平台.zip”之后,先理解前后端的边界在哪儿
把基于React+SpringBoot的前后端分离项目-校园社交平台.zip下载回来后,大多数人第一反应是解压、打开 IDEA、等 Maven 下载,最后卡在“端口冲突”或“数据库连不上”上。这类压缩包真正的价值不在那几万行源码,而在于它已经把“什么样的前后端协作方式能跑通一个社交产品”做成了范本。解压之后你会看到两套彼此独立的工程:一套 React 前端管页面、路由和用户交互,一套 SpringBoot 后端管登录态、发帖、评论、关注关系以及文件存储。它们不共享进程,也不共享代码,唯一连接通道是 HTTP 接口上的约定——路径怎么定、Token 放哪个 Header、返回体长什么样。把这个约定先读懂,后续不管是本地跑通、部署上线还是面试时被问“前端怎么处理后端下发的 401”,都能顺着同一条链路往下答。这篇就把这条链路从 zip 解压讲到能线上验证。
2. 从压缩包到可运行:React 前端与 SpringBoot 后端的启动链路
2.1 解压后先用四个标记文件辨认工程结构
我一般不建议直接双击解压到桌面,而是在终端里用unzip解压,顺便看一眼目录里到底装了什么。很多毕设包和课程设计包不会把 README 写得很规范,但pom.xml、package.json、application.yml、*.sql这四个文件基本不会缺席。
cd ~/projects unzip 基于React+SpringBoot的前后端分离项目-校园社交平台.zip -d social-platform cd social-platform find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "package.json" \ -o -name "application*.yml" -o -name "*.sql" \) 2>/dev/null | sort这个命令会列出前后端工程的入口文件和数据库初始化脚本。maxdepth 3是因为解压后可能还有一层外层目录,如果你直接解压到当前文件夹,第一层就是frontend/、backend/。看到输出后,可以按下表快速建立工程地图:
| 标记文件 | 所属端 | 在工程里扮演的角色 |
|---|---|---|
pom.xml | SpringBoot 后端 | Maven 项目入口,锁 JDK 版本和依赖版本 |
package.json | React 前端 | Node 项目入口,锁脚本命令与依赖 |
application.yml | SpringBoot 后端 | 数据源、端口、MyBatis、上传大小等运行时配置 |
schema.sql或init.sql | 数据库脚本 | 建库建表,也可能包含初始管理员账号 |
注意:如果package.json里同时出现webpack和react-scripts,说明前端是 Create React App 工程;如果出现vite,则是 Vite 工程。两者启动命令略有区别,后面配置代理时也有差异。
2.2 SpringBoot 后端启动前,application.yml 里四个键要重点核对
前端暂时可以不急着启动,先把后端跑起来。SpringBoot 项目启动失败的根因,大多数在数据源和端口这两块。下面这份配置是校园社交类项目最常见的形态:
server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/campus_social?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl四个容易引发连锁问题的点:
context-path: /api给所有后端接口加上了统一前缀。如果前端请求http://localhost:8080/api/user/login,而后端@RequestMapping写的是/user,那访问路径就是/api/user,两者要能对上。很多 zip 项目本身不加context-path,后端 Controller 直接写/api/**,这两种风格不要混用。- MySQL 8 及以上版本用
com.mysql.cj.jdbc.Driver;如果压缩包依赖的是 MySQL 5.x 驱动,项目可能无法连接到 MySQL 8。这种现象在“老 SpringBoot 2.x 项目 + 新版 MySQL”组合里尤为常见。 serverTimezone=Asia/Shanghai只解决 JDBC 连接时区,Jackson 序列化还需要另配spring.jackson.time-zone: GMT+8,否则前端看到的时间可能比数据库少 8 小时。multipart关系到头像上传。社交平台必然有图片上传,默认 1MB 往往不够,我习惯把max-file-size调到 10MB,max-request-size调到 20MB。
如果项目里既有mapper/*.xml又有注解 SQL,优先保留mapper-locations的配置。缺少这项时,SpringBoot 启动不会报错,但调用查询接口会直接 404,浏览器里以为是接口不存在,实际上是 Mapper XML 没有被加载。
2.3 React 开发服务器的跨域和代理转发
后端在 8080 跑起来后,接下来启动 React。前端开发服务器默认端口是 3000(CRA)或 5173(Vite),和后端 8080 不在同一个源,浏览器会拦截跨域响应。处理办法有两种:后端配 CORS,或者前端开发服务器配代理。zip 项目里最常见的是“前端代理 + 后端不完全放开 CORS”的搭配,这样更接近生产环境的表现。
CRA 项目可以直接在package.json里写proxy:
{ "name": "social-web", "proxy": "http://localhost:8080", "scripts": { "start": "react-scripts start", "build": "react-scripts build" } }Vite 项目则在vite.config.ts中配置:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })changeOrigin: true的含义是让代理服务器把请求头里的Host改写成目标地址的Host。它解决的是后端无法识别来源域名的问题,但和浏览器的同源策略没有直接关系。浏览器看到的请求始终发往localhost:5173,所以开发模式下前端不会触发 CORS 报错。真正的 CORS 发生在两种场景:一是前端直接请求后端绝对地址,不走代理;二是生产环境前后端不同域名。因此后端代码里往往还是要保留一个 CORS 配置类,只是生产环境要把allowedOrigins收紧。
2.4 用一条请求链路验证“分离”已经打通
前后端都启动后,不要急着点页面,先用 curl 验证接口再打开浏览器。这样能把“前端问题”和“后端问题”切开。
# 后端健康检查,-i 表示打印响应头 curl -i http://localhost:8080/api/health # 只看状态码和耗时,适合反复执行 curl -s -o /dev/null -w "HTTP %{http_code}, %{time_total}s\n" \ http://localhost:8080/api/posts?page=1如果health返回 JSON,posts返回 200,说明 SpringBoot 侧的数据源和路由是通的。此时再打开http://localhost:5173登录页面,F12 看 Network 面板,找到user/login这条请求,确认 Request URL 是http://localhost:5173/api/user/login且响应为 JSON。如果看到的是http://localhost:8080/api/user/login且报 CORS,则说明前端页面里把请求地址写死了,没有走代理,需要改回相对路径/api。
3. 校园社交核心链路:登录、发帖、关注里前后端必须对齐的三件事
3.1 Token 在 React 端怎么存、怎么带,401 又该怎么收
登录是社交平台的第一个请求,也是前后端协作方式的分水岭。这个 zip 项目多数采用 JWT + localStorage 方案:后端登录接口校验账号密码,返回一个加密 Token;前端把 Token 存在浏览器 localStorage,后续每个请求在Authorization: Bearer <token>里带上。它的特点是实现简单、适合毕设和中小型项目,但真正的生产系统很少只用 localStorage,因为 XSS 脚本一旦注入,Token 可以直接被读走。企业级做法是 httpOnly Cookie + CSRF Token,你需要在接手时知道这种差异。
React 侧最常见的封装是 axios 拦截器。用拦截器统一注入 Token,统一处理 401,而不是在每一个页面里手动判断:
// src/api/http.ts import axios from 'axios' const http = axios.create({ baseURL: '/api', timeout: 10000 }) http.interceptors.request.use((config) => { const token = localStorage.getItem('campus_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) http.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { localStorage.removeItem('campus_token') window.location.href = '/login' } return Promise.reject(error) } ) export default http这里有个容易被忽略的细节:response => response.data。如果后端返回体统一是{ code, message, data },那么在拦截器里直接剥掉外层,页面调用时拿到的就是业务数据。代价是如果后端某个接口没有按统一结构返回,比如文件上传接口直接返回字符串,页面侧的类型就会错位。所以这个拦截器的工作前提是后端ApiResult结构足够统一。
3.2 数据库脚本、实体和前端表单常见的三个“对不上”
拿到 zip 项目后,最容易让前后端开发吵起来的不是接口命名,而是三层结构之间的字段不一致:数据库字段、Java 实体属性、前端表单字段。三个最典型的问题:
第一,create_time字段。数据库里通常设置了DEFAULT CURRENT_TIMESTAMP,但 Java 实体没有@TableField(fill = FieldFill.INSERT),前端以为后端会返回时间,结果接口返回null。检查办法是打开数据库看建表语句,再和后端实体注释比对。
第二,逻辑删除字段。很多社交项目并不真正删除帖子,而是用deleted字段标记。如果 SQL 脚本里有这个字段,而 Mapper 里的查询没加WHERE deleted = 0,就会出现“帖子没了但评论还在”“关注人数包含已注销用户”的幽灵数据。MyBatis-Plus 可以在实体字段上加@TableLogic,但如果你用的是原生 MyBatis,就必须手工写条件。
-- 在存量数据上补逻辑删除位 ALTER TABLE post ADD COLUMN deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0正常 1删除'; -- 兜底历史数据 UPDATE post SET deleted = 0 WHERE deleted IS NULL;第三,时间序列化格式。SpringBoot 默认把LocalDateTime序列化成2025-02-06T21:30:00,前端想要的是2025-02-06 21:30:00。两种格式都能用,但前端列表组件如果做了排序或格式化,就会显示异常。建议在后端统一配置。
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+83.3 返回体约定好了,React 类型定义才能少写“any”
一个前后端分离项目维护成本高不高,看接口返回体就知道。校园社交平台这种业务,如果每个接口返回的字段名不统一——有的返回data,有的返回result,有的错误信息放在msg里,React 侧就会写出一堆临时类型。接手后要先做一件事:把后端 Controller 返回类型收敛到一个ApiResult<T>。
@Data public class ApiResult<T> { private Integer code; private String message; private T data; public static <T> ApiResult<T> ok(T data) { ApiResult<T> result = new ApiResult<>(); result.setCode(200); result.setMessage("ok"); result.setData(data); return result; } public static <T> ApiResult<T> fail(Integer code, String message) { ApiResult<T> result = new ApiResult<>(); result.setCode(code); result.setMessage(message); return result; } }对应地,前端抽出一套可供复用的类型定义:
export interface ApiResult<T> { code: number message: string data: T } export interface Post { id: number content: string userId: number nickname: string likeCount: number commentCount: number createTime: string } export const isSuccess = <T>(result: ApiResult<T>) => result.code === 200这样在 React 组件里调用getPostList()时能拿到完整的类型推导,而不是一个any。类型对齐之后,像“关注后按钮没立刻变亮”“点赞数重复累加”这类问题,往往不用调页面代码,先看后端返回的data结构就能定位。
4. 把校园社交平台部署到云主机:React 构建产物与 SpringBoot 打包
4.1 单端口部署:Nginx 托管 React 静态资源并反代 /api
本地开发时前端 5173、后端 8080 双端口没有任何问题,但部署到阿里云这类云主机时,如果直接把前后端两个端口都暴露给公网,要么被安全组拦,要么被扫描器盯上。生产部署最标准的方案是:React 构建成静态文件,交给 Nginx;SpringBoot 打成一个 jar,后端只监听内网或本机端口。Nginx 同时负责托管前端页面和反向代理/api请求。
先在前端工程目录构建:
cd frontend npm install npm run build构建产物默认在frontend/dist。把整个dist目录上传到服务器/data/campus-social/frontend/dist,后端 jar 放到/data/campus-social/backend,然后配置 Nginx:
server { listen 80; server_name your-domain.com; # React 构建产物目录 root /data/campus-social/frontend/dist; index index.html; # API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 带 hash 的静态资源可以长缓存 location /static/ { expires 7d; add_header Cache-Control "public"; } # 前端路由回退:刷新 /profile/123 时不白屏 location / { try_files $uri $uri/ /index.html; } }关键点是location /api/的proxy_pass要写成http://127.0.0.1:8080/api/,而不是http://127.0.0.1:8080。如果少写末尾的/api/,后端收到的路径会变成//api/user/login之类,部分框架会直接 404。第二条try_files $uri $uri/ /index.html解决 React Router 的刷新白屏问题——用户访问/profile/101时 Nginx 找不到对应物理文件,就回退到index.html,由前端路由接管。
4.2 生产环境跨域检查点:把 CORS 收窄而不是关掉
同域部署之后,浏览器请求your-domain.com/api/**和your-domain.com/**同源,理论上不会再触发跨域。但很多项目部署后仍然报跨域,原因是页面里某张图片走的是http://localhost:8080/files/**,被搜索引擎或浏览器缓存记住了。排查思路是打开浏览器 Network,筛选CORS error的请求,看它的 Request URL 是相对路径还是绝对 IP。
后端 CORS 配置在生产环境要做两处调整:第一,把allowedOrigins从*改成正式域名;第二,allowCredentials如果是true,那么allowedOrigins不能写*,必须写具体的协议、域名和端口,否则浏览器直接拒绝。这个组合坑在 React + SpringBoot 项目里出现频率极高。
云主机的安全组只管网络层,不管应用层。部署后要在云控制台放行 80(HTTP)和 443(HTTPS),后端 8080 端口尽量不要对公网开放。否则别人可以绕过 Nginx 直接访问你的 SpringBoot 接口,JWT 密钥如果写死在application.yml里,接口数据等于裸奔。
4.3 部署后的验证清单与白屏定位
| 现象 | 检查命令 | 预期结果 |
|---|---|---|
| 首页打不开 | curl -I http://服务器IP/ | 返回200,content-type: text/html |
| 页面白屏 | `curl -s http://服务器IP/ | head` 看 HTML 中 JS 路径 |
| API 404 | curl http://服务器IP/api/posts | 返回 JSON 而非index.html内容 |
| 图片裂开 | curl -I http://服务器IP/files/xxx.jpg | 返回200,content-type: image/jpeg |
前端 React 打包时如果package.json里没配homepage,构建生成的静态资源路径可能是绝对路径/static/js/main.js,这在部署到域名根路径时没问题;如果部署到子路径,就需要把homepage设为子路径,否则白屏。另一个常见问题是后端 jar 里保存文件用的是本地绝对路径,例如D:/upload/,部署到 Linux 后路径失效,接口返回 500。
5. 接手一个“zip 项目”最容易踩的版本与工具链组合坑
5.1 Maven 首次构建失败,先怀疑本地仓库和 JDK 版本
用 IDEA 打开后端工程后,Maven 会自动下载依赖。这时最常见的报错不是代码错误,而是依赖下载失败或编译级别不匹配。先用命令行确认环境:
mvn -v java -version如果项目是 SpringBoot 2.7,要求 JDK 8 或 11,而你本机是 JDK 17,编译可能直接失败。SpringBoot 3.x 则要求 JDK 17 及以上。这类压缩包里的.mvn或pom.xml通常会写java.version,先看它再决定是否切换 JDK。
Maven 下载慢或失败时,在~/.m2/settings.xml里配置阿里云公共仓库镜像可以解决大部分问题。配置镜像不会改变项目依赖本身,只是换一个下载源。注意不要因为项目里某个依赖在镜像仓库找不到,就去改动pom.xml的版本号,很多时候是本地仓库里残留了损坏的 jar,删除.m2/repository下的对应目录后重新构建即可。
5.2 SpringBoot 版本太高与 MyBatis 自动建表的组合问题
很多网课项目停留在 SpringBoot 2.x,新手拿到后喜欢直接升级到 SpringBoot 3.x,结果 MyBatis 相关依赖直接报ClassNotFoundException。原因很简单:SpringBoot 3 基于 Jakarta EE,javax.*变成了jakarta.*,旧版mybatis-spring-boot-starter不兼容。如果坚持用 Boot 3,需要选mybatis-spring-boot-starter的 3.0 以上版本,或者直接用mybatis-plus-spring-boot3-starter。
“表不存在时自动建表”这个需求,在 SpringBoot 里有多种触发时机。常见做法是利用spring.sql.init在启动时执行 SQL 脚本:
spring: sql: init: mode: always schema-locations: classpath:db/schema.sql但要注意:mode: always表示每次启动都执行schema.sql,如果里面写的是CREATE TABLE而不是CREATE TABLE IF NOT EXISTS,第二次启动就会冲突。data.sql更危险,每次启动都会重新插入初始化数据,造成重复账号。对于校园社交平台这种频繁迭代的工程,我一般建议引入 Flyway 管理 SQL 脚本,把建表语句放进db/migration/V1__init.sql,Flyway 会记录执行版本,避免重复执行。虽然 zip 里通常没有这个依赖,但这是走向真实项目最值得补的一步。
5.3 VSCode 下 React 标签闭合与 ESLint 假报错
React 开发者经常搜“什么插件支持 React 标签闭合”,其实新版 VSCode 对 JSX 的支持已经很完整。如果是 TSX 文件标签不能自动闭合,检查设置里是否开启了自动闭合:
{ "javascript.autoClosingTags": true, "typescript.autoClosingTags": true }再配合 Auto Rename Tag 插件处理成对标签的重命名,体验上足够接近现代前端 IDE。项目里如果有eslint的红色波浪线,先区分是真错误还是规范提醒。比如react-hooks/exhaustive-deps会在useEffect依赖数组不完整时报警,这不影响编译。真正危险的是把 ESLint 直接关掉,导致接手的同事看不到错误。折中办法是在.eslintrc.cjs里把暂时不严格的规则设为warn,开发时保留提示,提交前再处理:
module.exports = { rules: { 'react-hooks/exhaustive-deps': 'warn', '@typescript-eslint/no-unused-vars': 'warn' } }6. 用一个体检脚本收尾:解压后五分钟完成端口、构建与接口检查
前面所有配置都做完后,手动点浏览器验证效率太低。我习惯在项目根目录放一个check.sh脚本,把“后端在听、jar 已构建、前端产物存在、核心接口 200、静态资源可访问”这几项串成一次冒烟检查:
#!/usr/bin/env bash set -euo pipefail BASE_URL="${1:-http://localhost:8080/api}" DIST_DIR="${DIST_DIR:-./frontend/dist}" JAR_FILE="${JAR_FILE:-./backend/target/*.jar}" echo "== 1. 后端端口检查 ==" if lsof -i :8080 -sTCP:LISTEN >/dev/null 2>&1; then echo "[OK] 8080 正在监听" else echo "[FAIL] 8080 没有服务,先启动 SpringBoot" fi echo "== 2. 构建产物检查 ==" test -n "$(ls $JAR_FILE 2>/dev/null)" && echo "[OK] 后端 jar 存在" || echo "[FAIL] 后端 jar 缺失" test -f "$DIST_DIR/index.html" && echo "[OK] 前端 dist 存在" || echo "[FAIL] 前端未执行 build" echo "== 3. 核心接口检查 ==" curl -s -o /dev/null -w "health -> HTTP %{http_code}\n" "$BASE_URL/health" curl -s -o /dev/null -w "posts -> HTTP %{http_code}\n" "$BASE_URL/posts?page=1" echo "== 4. 登录接口带参数验证 ==" curl -s -X POST "$BASE_URL/user/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' \ -w "\nlogin -> HTTP %{http_code}\n"脚本里的set -euo pipefail让任何一步失败就停止,适合在持续集成里直接调用。BASE_URL设计成第一个参数,本地验证传http://localhost:8080/api,上线前传线上域名,同一套脚本可以同时充当部署后的回归基础。实际使用中建议把第三步的 HTTP 状态码和 curl 的退出码一并校验,例如curl_status=$(curl -s -o /dev/null -w "%{http_code}" ...),只有等于 200 才让脚本以 0 退出。这条链路跑通之后,React + SpringBoot 的校园社交平台无论从 zip 解压到云主机,都有了可重复验证的基线。
本文还有配套的精品资源,点击获取