资讯动态

SpringBoot+Vue前后端分离的艺术作品展示平台实战解析

发布时间:2026/10/20 7:31:47 来源:尧图企业网站定制
前阵子有个朋友跟我聊起一个想法他们学院每年都有一次艺术作品展线下办一次要租场地、布置展板展期一过作品就收进仓库后面基本没人再看。他说想做一个线上展示平台让作品能一直挂在“展厅”里老师同学随时能点开看还希望能按类别浏览、收藏点赞。聊到最后方向就敲定为一个基于SpringBootVue的艺术作品展示平台。这个项目名字听起来像是典型的全栈练手项目但真把它从头到尾落地一遍里面需要想清楚的细节其实不少。技术选型怎么定、表结构怎么设计、图片上传后如何访问、前端怎么处理分页和大图预览、本地开发怎么解决跨域、上线之后怎么部署每一环都能踩出几个坑来。这篇文章我把整个设计和实现过程完整复盘一遍从架构思路讲到具体代码再到排查过的几个典型问题给准备做同类项目或者正在学习前后端分离开发的朋友做一个参考。适合那种已经会SpringBoot单体增删改查、会Vue基础指令但还没有完整走通一个前后端分离项目的人。1. 需求拆解与技术选型为什么是SpringBootVue1.1 这类展示平台到底要解决什么问题一开始如果不把需求讲清楚很容易把项目做成一个“艺术作品后台管理系统”或者“艺术作品信息展示网页”看起来都有道理但用起来感觉很别扭。艺术作品展示平台的核心不是“管理”是“展示”。管理后台只是运营手段用户看到的首页、列表、详情才是整个产品的主体。围绕这个定位我梳理出三个核心用户场景普通用户进到首页希望一眼看到最新发布的作品按国画、油画、书法、雕塑、摄影等分类快速找到感兴趣的内容。点进作品详情页能看清作品大图、作者、创作说明还可以收藏、点赞、评论。运营者需要一个后台用来发布新作品、编辑作品信息、上传多张展览图片、管理分类和用户评论。这个需求模型一旦明确整个项目的功能边界就清楚了用户端以浏览与互动为主管理端以内容维护为主。后续所有的表结构设计、接口规划和页面设计都围绕这个模型展开不会再被“要不要做成电商下单”这类无关需求带偏。1.2 技术栈选型背后的考虑说实话这种展示类平台用传统服务端渲染方案也能做比如直接用一套模板引擎或者用PHP的后台框架开发速度还更快。但我最终选择SpringBoot Vue的前后端分离结构主要基于三点考虑。从分工角度讲前后端分离之后页面交互和接口逻辑互不干扰。前端可以专心处理懒加载、滚动加载、大图预览这些交互问题后端只用稳定输出JSON数据。从接口复用角度讲平台以后如果要做小程序端或者移动端H5后端接口可以原样复用不需要为一套页面重写服务端逻辑。从学习价值角度讲前后端分离是目前主流团队协作模式把这个流程完整走一遍比单纯学会一个框架更有迁移价值。后端的具体选型我用了SpringBoot 2.7 MyBatis-Plus MySQL。这里要提醒一下虽然SpringBoot 3.x已经发布一段时间了但很多生态组件和教程仍然以2.x为主如果目标是快速把项目跑通并且少踩版本坑直接用2.7搭配JDK 8是最稳妥的组合。前端我选了Vue3 Element Plus。Vue3的组合式API在写列表加载和组件状态管理时明显更顺手Element Plus的组件风格也比较适合快速搭建后台界面。一个人在本地开发这个项目时前后端分离看起来比传统方案多了一道“跨域”处理环节但这正好是实际工作中最常见的技术场景。多花一两天把跨域原理和开发代理搞明白比遇到报错再临时搜索更值得。2 数据库设计与后端模块落地2.1 核心表结构设计与关系梳理后端部分我习惯先设计数据库因为表结构一旦定下来实体类、接口返回结构都会顺着往下走改起来成本最高。艺术作品展示平台的数据量不算大但关系一点都不少核心涉及用户、作品、分类、收藏、评论这五块。我实际采用的表结构大致如下表名主要字段说明userid, username, password, nickname, avatar, role, create_time用户表role区分管理员和普通用户categoryid, name, sort, create_time作品分类表sort控制展示顺序artworkid, title, category_id, cover_url, images_url, artist_name, description, view_count, favorite_count, status, create_time, update_time作品表images_url存JSON数组字符串favoriteid, user_id, artwork_id, create_time收藏表唯一约束(user_id, artwork_id)commentid, user_id, artwork_id, content, parent_id, create_time评论表parent_id支持楼中楼回复这里有一个值得展开的设计细节作品表里我用了cover_url存放封面图再用images_url存所有图片的JSON数组字符串。第一次做这个项目的时候我犯过一个典型错误——单独建一张作品图片表来存多张图。那套方案理论上更“正规”但实际用下来展示端需要多一次联表查询后端管理端要维护第二张表的增删改代码量多了不少。对于这个体量的平台直接把多张图片以JSON数组形式存进作品行简单直接查询效率也高。如果以后图片数量真的涨到需要独立管理再拆表也不迟。索引方面我给artwork表的category_id、status和view_count都加了普通索引。分类筛选是用户高频操作状态字段控制内容是否前台可见浏览量用于热门排序这些字段进查询条件的频率很高。2.2 后端分层与REST接口设计后端代码我按controller、service、mapper、entity四层来组织。很多人觉得项目小分层是浪费但分层的价值在项目做到后期才会体现出来——参数校验放controller业务逻辑放service数据访问只留在mapper层出了问题能按层排查不会在一个方法里堆几百行代码。接口路径设计上我采用了RESTful风格并且所有接口统一挂在/api前缀下POST /api/auth/register用户注册POST /api/auth/login用户登录GET /api/artworks分页查询作品支持categoryId、keyword、sortField参数GET /api/artworks/{id}获取作品详情同时让浏览量加1POST /api/artworks新增作品管理员权限PUT /api/artworks/{id}编辑作品管理员权限DELETE /api/artworks/{id}删除作品管理员权限POST /api/artworks/{id}/favorite收藏或取消收藏POST /api/artworks/{id}/comment发表评论统一返回结构是我特别坚持的一步。所有接口的响应都封装成{ code: 200, message: success, data: {} }这样的JSON体前端axios响应拦截器里只需要判断一次code值不需要每个页面单独处理各种不同的返回格式。刚开始图省事的人可能会直接把返回类型写成Map或者直接返回实体短期看是快了但维护期一旦接口变更前端会非常痛苦。登录认证这块用的JWT。用户登录成功后后端生成一个带过期时间的token返回给前端前端存进localStorage每次请求在axios拦截器里自动带上Authorization头。后端用一个拦截器统一放行登录、注册接口和作品查询等公开接口其余操作校验token。这里要重点注意JWT里不要放敏感信息我通常只放userId、username和role。密码用BCrypt加密存储绝对禁止明文入库。2.3 图片上传与静态资源映射图片上传是整个平台最容易被忽略却又最容易出问题的环节。用户上传的不只是信息而是实实在在的文件文件存哪里、怎么命名、怎么访问每步都有讲究。我采用的方案是把图片保存到服务器本地磁盘目录使用UUID做文件名舍弃用户原始文件名。原因有两个一是防止文件名包含中文和特殊字符导致URL编码问题二是避免重名覆盖。上传时限制文件大小不超过2MB只接受jpg、png、webp格式这个限制在管理端上传组件上用代码写死。文件存储目录我用的是项目外部的一个固定目录比如/data/art-platform/upload/而不是项目运行目录下的相对路径。用相对路径在本地开发调试时没问题一旦打成jar包部署临时目录和jar解压目录都可能导致图片丢失或路径错乱。这一点是实战中大量踩坑后总结的经验。上传接口处理完文件后返回给前端一个可以直接访问的完整URL。后端配置了静态资源映射把磁盘上的上传目录映射成/api/upload/**这个URL路径前端拿到URL直接就能在img标签或el-image组件中展示。Configuration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/api/upload/**) .addResourceLocations(file: uploadDir /); } }这里有个细节要留意URL路径末尾的/**和本地路径末尾的/都不能省略漏一个都会出现请求路径拼接错误。实测中最常见的现象就是浏览器访问图片404检查后往往发现是资源映射时少写了结尾斜杠。3 前端实现与交互细节3.1 项目骨架与请求层封装前端我用了Vite构建Vue3项目目录结构按功能划分而不是按页面堆文件。src/api目录放所有接口调用src/views放页面组件src/router集中管理路由src/utils放请求封装和公共工具函数。请求层封装是整个前端的地基。我把axios实例单独抽出来统一配置了baseURL为/api这样后面所有接口路径只需要写/artworks、/auth/login这种相对路径。请求拦截器里从localStorage取token并设置到请求头响应拦截器里对HTTP 401统一做跳转登录页处理。这样写完之后每个页面组件里不需要关心token怎么带、登录过期怎么处理只负责调接口和渲染数据。// src/utils/request.js import axios from axios; const request axios.create({ baseURL: /api, timeout: 10000, }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); request.interceptors.response.use( response { if (response.data.code 401) { localStorage.removeItem(token); window.location.href /login; } return response.data; }, error { return Promise.reject(error); } ); export default request;路由配置里我用了懒加载组件在访问对应路径时才真正加载。作品详情页这种包含大量图片的页面用懒加载特别有效首屏体积能明显降下来。3.2 作品列表页与分类筛选作品列表页是整个项目最重要的页面用户在首页停留的时间基本都消耗在这里。我用卡片式瀑布流布局来展示作品每个卡片包含封面图、作品标题、作者和收藏数量。视觉上要克制艺术类内容不适合堆太多装饰元素干净的卡片更突出作品本身。分类筛选我用顶部的标签组实现。点击不同分类时前端重新请求第一页数据。这里有一个小优化为了避免用户快速连续点击分类产生多批无效请求我在切换分类时加了一个简单的取消旧请求处理。如果当前有未完成的请求新请求发出前把旧的取消掉。实测下来非常有效网络慢的时候不会出现数据错乱。分页方案上我没有用传统页码条而是用了“加载更多”。用户滚动到页面底部时自动加载下一页这种模式更适合图片流式的展示场景。判断滚动到底部的方法需要注意兼容性核心逻辑是document.documentElement.scrollTop window.innerHeight document.documentElement.scrollHeight - 100。这个100像素的提前量很关键它让加载动作在距离底部还有一段距离时就触发而不是等用户精确滚到底部。封面图统一使用el-image组件并开启lazy属性。Element Plus的图片组件懒加载底层用的是IntersectionObserver比手动监听滚动再判断图片位置要可靠得多。使用懒加载后首页即使有几十张作品封面初次加载也只会拉取视口附近这些图片页面打开速度提升非常明显。3.3 作品详情与收藏交互详情页分为三块顶部是作品大图区中间是作品基础信息和创作者说明底部是评论区。作品大图区我用了横向多图切换。因为作品可能不止一张图我用了一个轮播组件来展示images_url里解析出来的图片数组。图片加载失败的情况要认真处理我给每张图片加了fallback占位图避免用户看到裂图图标。收藏交互看似简单但连点问题很常见。用户快速点击收藏按钮可能连续触发多个请求导致后端收藏状态出现重复数据。我在前端对收藏接口加了请求锁请求未返回前按钮保持loading状态不再响应点击。后端同时用唯一约束兜底双保险保证即使前端异常也不会产生多条收藏记录。评论区支持一级回复。发表评论后需要重新加载评论列表但我不想整页刷新打断用户浏览体验所以采用了局部刷新方式只重新请求评论分页接口保持作品图片和信息区域的状态。后台管理端我单独做了一套布局管理员登录后能看到封面概览、作品管理、分类管理三个核心页面。作品管理使用表格展示点击编辑弹出表单表单里集成图片上传组件支持多图上传和拖拽排序。发布新作品时封面必须上传否则不允许提交这个校验在前端表单规则和后端参数校验里各做了一遍。4 前后端联调与本地部署4.1 开发环境的跨域处理所有接口调试完进入页面联调阶段遇到的第一个拦路虎一定是跨域。前端页面跑在5173端口Vite默认后端接口跑在8080端口两边端口不同浏览器会触发同源策略限制。解决跨域有两个方向后端配置CORS或者前端开发服务器做代理。我在本地开发阶段用的是前端代理方案也就是让Vite开发服务器把/api开头的请求转发到后端的8080端口。这样做的好处是后端不用为开发环境单独写CORS配置前端代码里也不用写完整的带端口的后端地址所有请求路径从浏览器角度看都是同源路径。在vite.config.js里配置export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, }, }, }, });这个方案在处理图片URL时也有额外好处。后端返回给前端的图片URL如果是/api/upload/xxx这种相对路径格式在前端代理环境下也能正确代理到后端图片加载不存在端口不一致的问题。4.2 生产环境打包部署生产环境部署的选择比较多可以前后端各跑一个独立服务也可以让后端同时托管前端打包后的静态文件。我这里推荐用Nginx做前端静态资源服务器同时充当反向代理转发/api请求到后端。打包前要处理一个问题前端通过Vite打包生成的静态资源路径默认是相对路径在部署到子目录时可能会出问题。我在vite.config.js里设置了base: ./这样构建出来的资源全部使用相对引用放在任意路径下都能正确加载。后端部署相对简单使用Maven打成jar包指定生产环境的配置文件把端口设置成8080图片上传目录设置为服务器上的绝对路径。用nohup java -jar xxx.jar --spring.profiles.activeprod方式后台启动即可。Nginx的核心配置片段如下server { listen 80; server_name your-domain; root /opt/art-platform/dist; index index.html; 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; } }try_files这行是关键Vue是单页应用刷新某个路由时Nginx需要把请求回退到index.html否则会出现“404 Not Found”。5 实战踩坑与前后端联调常见问题5.1 典型问题排查记录整个项目从开发到部署我整理了下面这几个高频问题每个都实际遇到过排查思路和解决办法直接放出来。图片上传成功但页面显示404。这个问题曾经困扰了我一晚上。上传接口明确返回了图片URL浏览器单独访问也是404。后来检查发现是后端静态资源映射目录和实际存储目录不是同一个本地开发用的是项目相对路径./upload联调时前端访问的路径多了一层/api而后端映射的URL前缀是/api/upload前缀不匹配导致404。把上传目录改成外部绝对路径、统一映射前缀后解决。前端分页无效接口一次性返回全部数据。排查发现是MyBatis-Plus分页插件没有正确注册。MyBatis-Plus在3.5版本以后必须显式添加分页插件Bean否则分页方法虽然执行不报错但查询结果不会真正分页。这个坑藏在配置阶段不报错反而更难发现。管理员发布作品后前台查不到。这是状态字段设计不统一导致的。作品表有status字段0代表待审核1代表已发布。管理员发布时前端传了整个表单后端接口里漏了status赋值导致作品一直是0状态前台默认只查询1状态的数据自然看不到。后来我在新增接口中显式对status赋值并在后台列表加了状态列的筛选。登录后刷新页面用户信息丢失。我在登录成功后把用户信息存进了Vue的响应式状态里但没同步到localStorage页面一刷新状态清空界面就显示为未登录。正确做法是登录时把用户基本信息也存一份到localStorage页面刷新后先读本地数据回填状态再向后端请求一次校验token有效性。跨域配置与代理同时存在导致请求异常。联调过程中后端同事加了一套CORS全局配置前端代理也保留着结果浏览器里偶发出现“Multiple CORS header”的报错。解决方案是开发环境保留前端代理、后端关闭CORS生产环境统一走Nginx反代双侧不叠加。5.2 实际优化与体验提升清单项目能跑通只是第一步上线前的体验优化我做了几件事每一项都对最终展示效果有明显帮助。列表页查询语句不要把所有字段都查出来。作品表中的description字段可能很长列表页根本展示不下。我用MyBatis-Plus的select方法只选择列表需要显示的字段详情页再查完整字段响应体体积小了不少。浏览量统计采用内存更新加定时落库的策略。用户每次打开详情页前端调一次浏览接口后端先把浏览量在缓存中加1每隔一段时间批量同步到数据库。这个体量的项目不引入Redis也可以用一个带时间锁的Map集合就能实现。这样避免了频繁update数据库接口响应速度也更快。图片资源按用途做了分级处理。封面图在管理端上传时会同时生成一个压缩版本列表页用压缩图详情页展示原图。压缩图不一定非要引入复杂的图形库Java自带的ImageIO类就能做等比缩放到指定宽度。前端部署时把静态资源做成强缓存策略。文件名里包含了内容哈希更新后URL变化自动绕过缓存不需要手动清缓存。评论区做了加载状态优化。用户点击“加载更多评论”时按钮变成loading状态并且用时间戳纪念防止重复点击。虽然是小细节但实际体验差异非常明显。后端接口统一加了统一的异常处理器。数据库异常、空指针、参数校验错误都能转换成统一的JSON返回前端拿到code不等于200时弹出相应提示而不是浏览器直接报错白屏。这个处理属于上线前必做项。最后再分享一点实际体会整个项目做下来我最大的体会是这类展示平台真正出彩的地方不在某个复杂算法或高深框架而在于把“展示”这个动作做得够不够细腻。图片加载够不够快、列表滚动是否流畅、收藏交互有没有防连点、管理员传图流程是否顺利这些细节叠加起来才是用户愿意继续浏览的原因。如果你也准备做类似的前后端分离项目我最想提醒两件事。第一技术选型不要太贪新选自己熟悉、社区资料丰富、能稳定跑通的组合把精力花在打磨业务场景上。第二多留意那些“不报错但很别扭”的现象比如分页像是没生效、图片偶尔变裂图、发布的内容前台看不到这些往往不是代码写不出来而是接口约定或者字段状态没对齐。逐层排查一遍项目的健壮性和你对全栈链路的理解都会提升一个档次。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑