☰
SpringBoot+Vue前后端分离开发实战:古典舞交流平台全栈实现
2026/9/26 5:02:27 网站建设 项目流程

1. 项目背景与整体思路

这个项目最开始源于一个很具体的需求:一群古典舞爱好者在社群里聊得热火朝天,但手头的作品、教学视频、练习心得都散落在各个社交平台上,没有一处能真正沉淀下来。有人发一段练习视频还要先导到网盘再甩链接,有人想找某个舞种的系统教程得翻几十个帖子,更别说想找一位靠谱的舞蹈老师互相交流了。

于是就有了做这个"古典舞在线交流平台"的想法。系统定位不是做视频网站的翻版,而是围绕古典舞爱好者的小圈子,做一个集作品展示、教学分享、互动交流于一体的垂直社区。技术方案选定前后端分离架构,后端用SpringBoot + MyBatis + MySQL,前端用Vue全家桶,整体是一套教科书级的主流组合。

这篇文章会把整个项目的来龙去脉讲透,从表结构设计到接口开发,从前端页面到部署上线,把我在实际开发中踩过的坑、验证过的方法都整理出来。适合有一定Java和前端基础的读者,想完整跑通一套前后端分离项目,或者正在准备毕业设计、个人作品集的开发者。

1.1 为什么锁定这个选题

古典舞这个方向其实是个很好的垂直切入场景。它有明确的用户画像——喜欢古典舞的人群往往对传统文化有认同感,有持续的内容消费需求,而且非常愿意参与互动和分享。从技术角度来说,这类内容平台天然需要用户系统、内容管理、评论互动、搜索筛选、个人主页这些标准模块,能覆盖大多数Web开发的核心知识点。

对比做一套泛娱乐视频网站,垂直领域的古典舞平台反而更容易把功能做深。比如舞蹈视频需要按舞种分类(汉唐舞、敦煌舞、昆舞、水袖舞等),需要按难度分级(入门、进阶、表演级),这些细分逻辑让数据库设计和业务接口设计都更有层次感,做出来的东西不是那种一眼假的"万能模板"。

1.2 前后端分离架构是必然选择

为什么坚持前后端分离而不是传统的服务端渲染?最直接的原因是开发效率。前端路由、组件化开发、状态管理这些能力让页面交互的实现成本低很多,尤其像视频列表无限滚动、搜索联想、评论区实时回复这种高频交互,用Vue做比用模板引擎舒畅太多了。

部署层面的优势也明显。前端打包成纯静态文件扔给Nginx,后端打成一个jar包独立运行,两边互不干扰。后端出问题只需要重启Java进程,前端发新版只需要替换静态文件,这在后期迭代维护时非常重要。还有一个隐性好处:前后端分离之后,接口天然就是RESTful风格,后续如果要做小程序端或者App端,前端整套逻辑都可以复用,不用另起炉灶。

2. 技术栈选型与架构设计

技术在选型的时候,我一度纠结过要不要上微服务、要不要用Redis做缓存、要不要引入消息队列。后来把这些想法全砍掉了,守住一条底线:这个体量的项目,用最简单的主流技术栈把它做扎实,比堆砌一堆花架子有价值得多。

2.1 后端三件套的取舍逻辑

SpringBoot负责整体框架装配。它的自动配置机制省掉了大量XML配置,内嵌Tomcat让部署变成一条java -jar命令的事。版本我用的是SpringBoot 2.7.x,这个版本兼容性很好,JDK用8或者11都能跑,不会出现JDK版本过高导致各种配置类报错的问题。如果你用3.x版本,注意JDK必须升到17,有些老项目依赖的第三方库可能还没适配。

MyBatis负责数据持久层。虽然JPA写起来更省事,但MyBatis对SQL的掌控力是它不可替代的优势。这个项目里有不少复杂查询,比如视频列表的多条件筛选(按舞种、按难度、按热度)、用户的关注与粉丝关系链查询,用MyBatis的XML映射文件可以精确定义每一条SQL,配合动态SQL标签能优雅地处理各种可选条件的拼接。

MySQL负责数据存储。版本选择8.0,字符集直接上utf8mb4,原因很实际:评论区和作品描述里经常出现生僻字和特殊符号(比如古琴谱字符),utf8mb4才能完整覆盖。存储引擎选InnoDB,因为业务里有大量并发读写,InnoDB的行级锁和事务支持是必须的。

2.2 Vue全家桶选型细节

前端用的是Vue 2 + Element UI。没上Vue 3的原因很现实:这套组合的生态最成熟,踩坑的参考资料最多,网上随便一搜就能找到解决方案。如果你从零开始学,Vue 2的教程资源也最丰富,等把这个项目吃透了再平滑过渡到Vue 3完全来得及。

Vue Router采用history模式,路由设计上要花点心思。页面结构分成访客可见的公共部分(作品广场、舞蹈课堂)、登录后可见的个人空间(我的发布、我的收藏)、以及管理员专属的后台管理。这三级路由的嵌套关系要理清楚,不然权限控制会越写越乱。

Vuex负责状态管理。用户的登录状态、Token信息、用户资料这些全局数据放进Vuex,避免组件之间层层传参。还有一个容易忽视的细节:页面刷新后Vuex数据会清空,需要配合localStorage持久化方案,否则用户明明登录过了,一刷新就变成游客状态,体验非常糟糕。

2.3 数据库表设计思路

整套系统最核心的是围绕用户和作品展开的几张表。用户表(user)除了基础账号信息,还需要区分角色类型,我用的方式是role字段:0代表普通用户,1代表舞蹈老师(可以发布教学课程),2代表管理员。这样做的好处是后期加权限拦截时逻辑清晰,不用额外建角色关联表。

作品表(work)是这个系统的灵魂。字段设计上预留了封面图URL、视频URL、标题、简介、舞种分类、难度等级、播放量、点赞量、评论量。这里有一个重要的经验:统计字段(播放量、点赞量、评论量)直接冗余在作品表里,而不是每次都去count聚合查询。虽然牺牲了一点数据一致性,但对读多写少的应用场景来说,性能提升是实打实的。数据不一致的问题可以通过定时任务或者异步更新来兜底。

互动表(comment、favorite、follow)各自独立。评论表的parent_id字段支持楼中楼回复;收藏表的联合唯一索引(user_id, work_id)防止重复收藏;关注表同样用联合唯一索引防止重复关注。这三张表的设计是典型的社交系统标配,做完这个项目你对关联查询和索引设计会有很直观的认知。

3. 核心功能模块拆解与实现

3.1 用户体系与权限控制

注册登录流程用的是JWT方案。用户输入账号密码后,后端验证通过就签发一个Token返回给前端,前端存在localStorage里。之后的每次请求都在Authorization请求头带上Token,后端通过拦截器统一校验。

这里有个很关键的细节:密码存储绝对不能用明文。我用的BCrypt加密,它是加盐哈希算法,每次加密结果都不同,但校验时能正确比对。有同学自己实现MD5加盐,其实没必要,Spring Security库里的BCryptPasswordEncoder直接拿来用就行,安全系数和易用性都比手写方案好。

权限拦截在后端用拦截器实现。写一个TokenInterceptor,在preHandle方法里校验Token有效性,再根据接口路径前缀判断是否需要特定角色。管理相关的接口统一以/admin开头,普通用户就算伪造请求也访问不了,这就是前后端分离项目相比于纯前端路由守卫更安全的根本原因——前端的路由守卫只是用户体验层面的控制,真正的安全防线在服务端。

3.2 舞蹈视频与作品发布模块

视频上传是这个模块的重头戏。文件上传接口接收前端传的MultipartFile,保存到服务器指定的磁盘目录,然后返回访问URL。生产环境下建议把视频文件存放在独立目录,与项目代码隔离,配合Nginx的静态资源映射对外提供访问。

视频上传有几个实用的判断逻辑要加上。文件大小限制用Spring的max-file-size配置,我这边限制单个视频500MB,超过就报错提示。文件格式用后缀名校验,只允许mp4、mov、avi这些常见视频格式。还有一个容易被忽视的点:视频文件可能会很大,前端上传时要注意配置axios的超时时间,默认的几秒肯定不够用,我设置的是不限制超时,改用进度条组件给用户反馈上传状态。

作品发布页面的表单校验也是细节活。标题必填且不超过50字,简介不超过500字,舞种和难度必须有默认值。我见过不少项目为了省事把非必填字段全部留空提交,结果列表页到处都是"未命名作品",观感极差。这块用Element UI的表单校验规则就能覆盖,成本很低。

3.3 互动交流功能设计

评论模块支持一级评论和二级回复。展示逻辑是:先加载作品下的所有顶级评论(parent_id = 0),每条顶级评论下面再加载它的回复列表。查询时我习惯用两次查询(先查顶级、再批量查回复),而不是一条SQL用子查询硬扛嵌套,因为评论回复的层级最多就两层,两次查询的性能和代码可读性都比嵌套SQL好。

点赞和收藏虽然业务逻辑不同,但实现思路一致:先查是否存在记录(联合唯一索引兜底),不存在就插入并把作品表的对应计数加一,存在就删除并把计数减一。这里要注意事务的问题,插入/删除和计数更新必须在同一个事务里,用@Transactional注解包起来,否则可能出现"已收藏但收藏数没变"的脏数据。

关注功能涉及用户关系链。关注操作做的事情分两步:往follow表插一条记录,然后异步更新用户的粉丝数和被关注者的关注数。这里我第一次做的时候踩了坑——直接在接口里同步执行两条更新,结果在关注量大的时候接口响应明显变慢,后来把计数更新的逻辑挪到异步线程池(简单用Spring的@Async注解)就解决了。

3.4 后台管理模块

后台管理我没有单独做一套前端工程,而是把管理页面嵌在同一个Vue应用里,通过路由前缀区分。用Element UI的el-menu做侧边栏导航,包含用户管理、作品管理、评论管理、数据统计四个页面。

用户管理做的事情是查看用户列表、禁用违法账号、重置密码。作品管理是审核新发布的作品(主要靠人工看封面图是否有违规)、下线违规作品。数据统计页面用ECharts展示近30天的注册量、作品发布量、活跃用户数趋势。管理员页面的数据都通过/admin前缀的接口获取,后端拦截器统一校验是管理员角色,这套机制很实用。

4. 关键接口与代码实现细节

4.1 后端接口设计规范

整个项目的接口遵循一套统一的返回格式,我定义了一个Result类:code(200成功、400参数错误、401未登录、403无权限、500服务器异常)、message(提示信息)、data(业务数据体)。所有接口都返回这个统一结构,前端拿到后先判断code再处理data。这样做的好处是前端的请求封装变得极其简单,axios拦截器里只需要统一处理非200的code,弹出对应的错误提示即可。

核心接口一览:

模块接口路径请求方式说明
用户/api/user/registerPOST用户注册
用户/api/user/loginPOST登录获取Token
用户/api/user/infoGET获取当前用户信息
作品/api/work/listGET分页获取作品列表
作品/api/work/detail/{id}GET作品详情和作者信息
作品/api/work/publishPOST发布新作品
互动/api/comment/list/{workId}GET获取作品评论
互动/api/comment/postPOST发布评论
互动/api/favorite/togglePOST切换收藏状态
互动/api/follow/togglePOST切换关注状态
管理/admin/user/listGET管理员查看用户列表
管理/admin/work/auditPOST作品审核

4.2 MyBatis动态SQL与分页

分页功能用的是PageHelper插件,这是MyBatis生态里最成熟的物理分页方案。使用方式很简单:在service层查询前调用PageHelper.startPage(pageNum, pageSize),紧接着的查询就是分页查询,返回一个PageInfo对象,里面包含了总条数、总页数这些分页元数据。插件底层是拦截器机制,自动在执行的SQL后面拼接limit子句,对业务代码几乎无侵入。

动态SQL处理多条件筛选是MyBatis的看家本领。作品列表页的筛选条件有舞种、难度、排序方式(最新/最热),用XML的<where>和<if>标签拼接:

<select id="selectWorkList" resultType="com.example.entity.Work"> SELECT w.id, w.title, w.cover_url, w.video_url, w.category, w.difficulty, w.play_count, w.like_count, w.comment_count, u.nickname as author_name FROM work w LEFT JOIN user u ON w.user_id = u.id <where> <if test="category != null and category != ''"> AND w.category = #{category} </if> <if test="difficulty != null and difficulty != ''"> AND w.difficulty = #{difficulty} </if> </where> ORDER BY <choose> <when test="sort == 'hot'">w.play_count DESC</when> <otherwise>w.create_time DESC</otherwise> </choose> </select>

这个SQL里有两个值得说道的地方:一是LEFT JOIN关联用户表直接查出作者昵称,避免在service层做二次查询;二是ORDER BY用<choose>标签做排序逻辑的切换,把排序策略放到SQL层面解决,就不用在Java代码里拼字符串了。

4.3 前端路由与状态管理

前端路由的设计要对齐页面的访问层级。我的路由结构长这样:

const routes = [ { path: '/', component: Layout, redirect: '/home', children: [ { path: 'home', component: HomeView }, { path: 'works', component: WorkList }, { path: 'work/:id', component: WorkDetail }, { path: 'teach', component: TeachCenter }, { path: 'user', component: UserCenter, meta: { requireAuth: true } } ]}, { path: '/admin', component: AdminLayout, meta: { requireAdmin: true }, children: [ { path: 'users', component: UserManage }, { path: 'works', component: WorkManage }, { path: 'stats', component: DataStats } ]} ]

路由守卫的处理要着重注意。全局前置守卫里做两级判断:先检查meta.requireAuth的路由,用户未登录直接redirect到登录页;再检查meta.requireAdmin的路由,非管理员账号全部拦截。前端守卫是用户体验的第一道门,但就像前面强调的,真正安全的门闩在后端拦截器那里。

状态管理用Vuex,模块划分成user和app两个模块。user模块持有token、userInfo、isAdmin,登录/登出都在这里触发action提交mutation。app模块管理一些全局UI状态,比如侧边栏折叠、全屏Loading开关。关键点是user模块的持久化:每次commit之后同步写入localStorage,store初始化时先从localStorage读取,不要用vuex-persistedstate这种额外插件,自己写几行代码就搞定了,依赖更少还更可控。

5. 本地开发环境搭建与常见坑

5.1 环境准备清单

本地开发环境建议按这个清单准备,版本号是最低要求:

工具版本要求说明
JDK1.8或以上推荐用JDK 8,稳定性最好
Maven3.6+依赖管理和项目构建
Node.js14+前端开发环境
MySQL8.0数据库
Navicat任意数据库可视化工具,也可以命令行
IDEA任意后端开发IDE
VSCode任意前端开发IDE

有两个环境变量容易搞混:Maven的MAVEN_HOME指向的是解压目录,Path里要追加的是bin目录;Node的PATH同理。配置完之后在命令行分别输入mvn -v和node -v验证是否生效。

5.2 数据库初始化和后端启动

打开Navicat新建数据库,名称命名为dance_platform,字符集选utf8mb4。然后用系统的SQL脚本文件直接导入建表语句和初始数据。这里有个经验:项目里要放一份完整的database.sql文件,包含全部建表语句和默认数据(管理员账号、测试用户、演示作品),这样其他人在本地部署时只需要两步操作——创建数据库、导入脚本,全程不超过一分钟。

后端启动过程是比较省心的。把application.yml里数据库账号密码改成自己的,直接运行主类的main方法。SpringBoot会内嵌启动Tomcat,默认监听8080端口。验证启动成功小技巧:启动日志里看到"Started Application in X seconds"就说明成功了一半,再访问http://localhost:8080/api/user/info测试接口是否返回JSON。

5.3 前端启动和常见问题

前端项目先执行npm install安装依赖,这个过程是最容易出问题的。主要有两个坑:

第一个是网络问题导致依赖安装失败。npm install卡在某个包上半天不动,最后报ECONNRESET错误,多半是网络不稳定。解决方式是换成国内镜像源,执行npm config set registry https://registry.npmmirror.com,再重新install,速度会有质的提升。

第二个是依赖版本冲突。有时候网上clone的项目用的是Element UI 2.x,你本地npm install装成了最新版,页面直接报错。这种情况锁版本就很重要,项目里应该把核心依赖的版本写死,比如"element-ui"固定为"^2.15.6"。

启动前端用npm run serve,devServer端口默认是8080,但后端已经占了8080端口,所以要在vue.config.js里把前端端口改成8081,同时配置代理转发/api路径到后端服务。这么配置的好处是开发阶段没有跨域问题的困扰,axios请求都走相对路径,由devServer转发到后端。

6. 项目部署实战

部署是衡量项目是否真正完工的标尺。我在部署阶段分别试了Linux服务器和Windows服务器两种方案,踩了不少坑,这里把完整流程和心得分享出来。

6.1 Linux服务器部署完整流程

Linux环境我用的是CentOS 7 + Nginx + Jar包的经典组合。整个部署路线是:前端打包成静态文件交给Nginx托管,后端打成一个可执行Jar包用systemd守护,Nginx把/api路径反向代理到本机的Java服务。

后端打包这步有一些细节要注意。在项目根目录执行mvn clean package -Dmaven.test.skip=true,跳过测试打包,最终在target目录得到xxx.jar文件。这个jar包传到服务器后用java -jar xxx.jar测试运行,如果有环境变量或配置问题日志会直接打印出来。确认能跑之后,再把它注册成systemd服务,保证服务器重启后Java进程能自动拉起。

前端打包执行npm run build,生成dist目录。整个dist目录传到服务器的/usr/share/nginx/html/下,然后修改Nginx配置。我这里贴一份关键配置:

server { listen 80; server_name your_domain.com; root /usr/share/nginx/html; index index.html; # 前端路由history模式配置 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 视频文件访问映射 location /upload/ { alias /data/upload/; } }

这个配置文件里有三个关键点,我分别说下踩过的坑。第一,history模式下刷新页面会404,原因是前端路由是浏览器端的,Nginx不知道/works这个路径对应的文件其实是index.html,所以必须用try_files把所有路径都兜回index.html。第二,/api反向代理后面不能丢掉proxy_set_header相关的头信息,否则后端拿不到客户端的真实IP,做访问统计或者封禁操作都会失效。第三,视频文件我放在/data/upload/,通过/upload/映射出去访问,这样Jar包和静态文件分开存放,日志和排错都很直观。

最后用nginx -t检查配置语法,通过后nginx -s reload生效。整套部署完成后,从浏览器打开服务器IP或者域名,前端页面能正常显示,接口能正常访问,项目就算正式上线了。

6.2 Windows服务器部署方案

Windows环境下的部署逻辑和Linux一样,但具体操作方式有差别。后端Jar包直接用cmd窗口java -jar运行,或者用WinSW这类工具把Java进程注册成Windows服务。前端dist目录放进Nginx的html目录后,修改conf/nginx.conf,配置逻辑和Linux版本完全一样。

Windows部署最大的坑是防火墙。默认情况下Windows防火墙会拦截外部对80端口和8080端口的访问,需要去"高级安全Windows Defender防火墙"里新建入站规则,放行这两个端口。我遇到过数次"程序明明在跑,但外网访问不了"的情况,排查到最后都是在防火墙上卡住。

6.3 部署过程踩过的坑

部署时最容易遇到的一类问题是环境不一致。本地代码跑得欢,部署到服务器就报错,最典型的有三个:

MySQL版本不一致导致的SQL语法错误。本地用MySQL 5.7写的SQL,上到8.0可能没问题,但反过来就可能报错,比如5.7的某些写法在8.0不兼容。解决方案是部署前让本地和服务器用同一个大版本,我这边统一用8.0。

JDK版本不一致导致的Class版本错误。本地用JDK 17编译的jar包,放到服务器的JDK 8环境运行会报UnsupportedClassVersionError。这属于低级错误但特别常见。打包前先检查目标环境的JDK版本,用maven的java.version属性控制编译版本。

资源路径硬编码问题。本地开发是Windows环境,文件路径用D:/xxx/upload,部署到Linux就找不到了。这个问题的根治方案是文件存储路径配置化,在application.yml里通过自定义属性配一个upload.path,线下和线上分别配不同的值。

7. 常见问题与排查技巧实录

7.1 高频问题速查表

把这些年在开发部署过程中遇到的高频问题整理成一张速查表,每一项都是真实发生过的场景。

问题现象可能原因排查命令/方法
前端请求/api报404Nginx未配置反向代理检查nginx.conf中location /api配置
跨域报错Nginx代理丢失或者前后端直连用Nginx统一代理,避免前端直连后端
上传视频报请求超时后端Tomcat未配置大文件超时检查spring.servlet.multipart配置
中文乱码数据库字符集不是utf8mb4检查数据库和连接的characterEncoding
刷新页面404Vue Router history模式未配置try_filesNginx配置try_files $uri $uri/ /index.html
接口返回401Token过期或未携带检查请求头Authorization是否正确
端口被占用多个Java进程或者Tomcat冲突netstat -tlnp | grep 8080 查PID
视频能上传但播放卡顿Nginx未配置大文件传输缓存location /upload/添加sendfile on; client_max_body_size

7.2 连接数据库的典型报错

数据库连接这块有几个报错几乎每个做这个项目的人都会遇到。java.sql.SQLException: Access denied for user 'root'@'localhost'多半是密码配错了,或者用户没有远程访问权限。MySQL 8.0默认的认证插件是caching_sha2_password,有些老版本的JDBC驱动不支持,会报Public Key Retrieval is not allowed,解决方法是连接字符串里加上allowPublicKeyRetrieval=true。

另一个高频问题是Communications link failure。如果确定数据库地址和端口没写错,优先检查服务器防火墙有没有放行3306端口。在本地开发时服务器MySQL没开的话,直接终端连接报错,这个判断很简单。

7.3 性能优化思路与实操

项目跑起来之后,性能优化是让系统真正能用的关键一步。最基础也最见效的优化是给热点查询加上合理的索引。作品列表页按播放量排序的场景,给create_time加普通索引效果不大,但给play_count加上索引后ORDER BY play_count DESC的查询可以从全表扫描变成索引扫描。评论列表按作品ID查询的场景,给comment表的work_id加上索引必须要有。

用MySQL的EXPLAIN命令分析慢查询SQL是个值得长期坚持的习惯。执行EXPLAIN SELECT...之后重点看type列和rows列,如果type是ALL就是全表扫描,说明索引没生效,需要回头检查索引设计。这套方法能让你把每条核心SQL的查询计划都摸得门儿清,对索引的理解会提升一个档次。

8. 代码层面的部分优化与一点经验总结

整个项目做完,回头复盘的时候发现有几个优化点是提高代码质量的关键。第一是全局异常处理的统一设计,我写了一个GlobalExceptionHandler,用@RestControllerAdvice统一捕获业务异常和未知异常,返回标准的Result格式。这个设计的价值在于,接口层永远不用再写try-catch,业务代码的异常抛出去后会自动被这个处理器接住并规范化,代码整洁度大幅提升。

第二是数据库层的扩展思维。现在收藏数、点赞数这些计数直接存储在作品表字段里,做大之后可以考虑引入Redis缓存计数或者用异步消息队列来降低数据库压力。但这里的取舍要想清楚:项目初期最需要的是逻辑直观和快速上线,过度设计带来的复杂度远大于收益,等真正出现瓶颈了再引入缓冲方案也不迟。

第三是配置分离的思想。环境相关的配置(数据库连接、上传路径、日志级别)统统放在application.yml,并且区分application-dev.yml和application-prod.yml两套配置,用spring.profiles.active参数来切换。大家创建项目的时候用一套配置跑到底,后期部署时被环境差异坑一次才会真正理解配置分离有多重要。

我在做这个项目的过程中最深的体会是:前后端分离项目真正的工作量不在写代码本身,而在把业务的边界想清楚。前端只关注页面和交互,后端只关注数据和逻辑,两者通过规范的接口契约协作,这个边界一旦模糊,就会陷入"前端等着后端改字段、后端等着前端调接口"的泥潭里。所以接口文档一定要在开发前定清楚,哪怕只是简单在笔记软件里列个表格,也比开发到一半再口头协商效率高得多。这个平台系统的价值在于它覆盖了一个完整业务系统从设计到上线全链路的核心问题,把这些经验消化掉,后面不管做什么类型的Web系统,都能少走不少弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询