搞过毕设或者自己接过类似后台管理系统项目的朋友应该都对 SpringBoot 不陌生。前阵子帮人完整搭了一套“钱币收藏交流系统”从需求梳理、数据库设计到前后端联调、打包部署踩了不少坑也沉淀了一些比较通用的思路。这篇就围绕这个选题把整个设计和实现过程掰开揉碎讲清楚包括数据库表怎么设计、权限怎么控制、帖子审核和图片上传这些核心功能怎么做以及最后 Vue 项目怎么打包塞进 SpringBoot 的静态资源目录里。不管你是拿这个题目做毕业设计还是单纯想练手一个 SpringBoot Vue 的全栈项目这篇应该都能给你一个可以直接照着做的路线。1. 钱币收藏交流系统到底在解决什么问题先聊点实在的。钱币收藏这个圈子其实挺特殊的——泉友之间非常依赖“交流”和“鉴定”。一枚币值不值钱、是不是洗过、是不是修补过光靠图片很难判断需要大量同好之间的讨论。而市面上的电商系统只解决了“交易”没有解决“交流”和“知识沉淀”。所以这个系统定位很明确做一个面向钱币收藏爱好者的社区型平台核心是藏品展示、讨论交流、经验分享顺便把个人收藏管理起来。理解到这个层面你就能明白为什么这类系统不能简单套一个通用的 CRUD 后台了。它的核心模块应该是这样几条线藏品管理用户把自己的藏品登记上去包括名称、版别、直径、重量、品相评分、图片、入手价格、购买渠道等。这是整个系统的“资产底座”。交流社区用户可以发布帖子比如“这枚大头是不是原光”“请各位帮忙看看这枚崇宁通宝的性质”其他用户能回复、点赞、收藏帖子。这是系统的“灵魂”。知识库/图鉴模块按朝代、币种分类整理钱币知识相当于一个轻量级百科既能沉淀内容又能吸引搜索引擎流量。个人中心我的藏品、我的帖子、我的回复、我收藏的帖子、系统通知。后台管理管理员审核帖子、管理用户、管理分类、管理首页 Banner 等。不过坦白说如果你是按毕设的标准来做建议把知识库模块砍掉或者弱化把精力集中在藏品管理 交流社区 后台审核这三块上。原因很简单交流系统的核心难点在“帖子审核”和“用户互动关系”图鉴类内容本质上是静态数据录入对技术展示帮助不大但工作量却不小。还有一点容易被忽略的是图片存储方案。钱币收藏非常依赖高清图一枚币往往要上传正面、背面、边齿、细节放大好几张图。如果每张图都直接存数据库或者塞进本地磁盘后期部署和备份都很头疼。我这边采用的是本地磁盘存储 数据库只存访问路径的方案用 Nginx 做静态资源映射实际部署时也很方便。这个我在后面专门讲。2. 技术选型为什么是 SpringBoot MyBatis Vue 这套组合现在很多毕设题目都直接带上了 SpringBoot 字样但这套系统真正要选型的其实不只是框架本身而是整套前后端方案的搭配。我的选择和理由如下技术组件选型选型理由后端框架SpringBoot 2.7.x稳定资料多规避 SpringBoot 3.x 版本太高导致的各种兼容坑持久层MyBatis-Plus单表操作不用写 SQL复杂查询手写 XML效率极高数据库MySQL 5.7 / 8.0免费、通用、面试常问适合做数据表设计展示权限认证Spring Security JWT比 Shiro 更主流token 无状态适合前后端分离前端Vue 2 Element UI生态成熟网上组件案例多做后台管理类界面最快构建工具Maven和 SpringBoot 整合最顺package 成 jar 一条龙图片存储本地路径 Nginx 映射简单可控适合中小型项目不引入 OSS 依赖和费用先说一个坑千万别用 SpringBoot 3.x 来写这类毕设。虽然新版本性能更好但你先要搞清楚 SpringBoot 3 基于 Jakarta EE 9javax 包名改成了 jakarta很多网上找的旧教程代码直接抄过来会编译报错。更麻烦的是 Spring Security 在 SpringBoot 3 里的配置方式变化很大一旦卡住你搜到的大多是英文资料或者过时答案。用 2.7.x 的话整个学习曲线能平缓 70%。再说说为什么用 JWT 而不是 Session。做前后端分离项目时前端部署在 Nginx 或者打包进 SpringBoot 静态目录后端只是提供 API 接口这种情况下 Session 跨域处理比较复杂——要配 CORS、要配同源策略、还要考虑 Cookie 跨域携带问题。JWT 就简单粗暴登录成功后后端把一个签名 token 发给前端前端每次请求时放在请求头里带回来后端解析验证就行彻底摆脱 Session 存储。MyBatis-Plus 也是强烈推荐的。一开始的时候我也纠结要不要用纯粹的 MyBatis后来想了想这套系统的核心业务在“帖子 回复 藏品”这三张表的联表查询单表 CRUD 占了可能 60% 的工作量用 MyBatis-Plus 直接省掉大量重复的 XML 编写。而且它的分页插件、条件构造器在写前台列表接口时真的能省很多事。3. 数据库设计钱币、帖子、回复三张核心表怎么建模数据库设计是整个系统里最见功底的环节。我见过太多人一上来就急着写代码结果做到帖子列表联表查询的时候才发现表结构不对回头大改非常痛苦。这里我直接把我最终定稿的核心表结构拿出来讲。3.1 用户表与角色分离用户表本身不复杂但有几个字段值得注意CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, role tinyint(4) NOT NULL DEFAULT 1 COMMENT 角色0管理员 1普通用户, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态0禁用 1正常, create_time datetime NOT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;两个容易被忽略的点一是密码不要明文存用 Spring Security 的 BCryptPasswordEncoder 做哈希二是角色字段用简单的 int 而不是单独建一张角色表。有些教程喜欢把用户角色拆成 user_role、role 两张表体现所谓的“标准化设计”其实对这个体量的系统完全没必要——管理员和普通用户就两种角色用 int 字段判断足够清晰简单答辩时也能讲明白。如果非要体现“扩展性”可以礼貌回应设计为可扩展的 int 类型等到真需要多种角色了再拆表。3.2 藏品表藏品表对应的是“我的收藏管理”功能字段设计上要贴合钱币收藏的实际习惯CREATE TABLE coin ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 所属用户, name varchar(100) NOT NULL COMMENT 钱币名称, dynasty varchar(50) DEFAULT NULL COMMENT 朝代/时期, category varchar(50) DEFAULT NULL COMMENT 分类如方孔圆钱/银元/铜元/纸币/机制币, version_desc varchar(100) DEFAULT NULL COMMENT 版别描述, diameter decimal(10,2) DEFAULT NULL COMMENT 直径(mm), weight decimal(10,2) DEFAULT NULL COMMENT 重量(g), grade varchar(20) DEFAULT NULL COMMENT 品相评分, purchase_price decimal(10,2) DEFAULT NULL COMMENT 入手价格, source varchar(100) DEFAULT NULL COMMENT 购买渠道/来源, description text COMMENT 详细介绍/背景故事, cover_image varchar(255) DEFAULT NULL COMMENT 封面图, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 审核状态0待审核 1通过 2驳回, create_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里要特别强调status字段。很多收藏系统的藏品没有审核流程用户传什么就直接展示什么这是不对的——因为藏品页涉及价格、品相这些可能产生交易纠纷的信息必须经过管理员审核才能公开展示。这个字段也为后面做“待办审核”功能提供了一个清晰的数据基础。3.3 帖子表与回复表交流社区的核心是帖子表这里的设计决定了后面做分页列表、热门排序、帖子详情这些功能时 SQL 好不好写CREATE TABLE post ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 发帖人, title varchar(200) NOT NULL COMMENT 帖子标题, content text NOT NULL COMMENT 帖子正文, images varchar(2000) DEFAULT NULL COMMENT 图片地址多张用逗号分隔, category_id bigint(20) DEFAULT NULL COMMENT 所属版块分类, view_count int(11) NOT NULL DEFAULT 0 COMMENT 浏览量, like_count int(11) NOT NULL DEFAULT 0 COMMENT 点赞数, reply_count int(11) NOT NULL DEFAULT 0 COMMENT 回复数, is_top tinyint(4) NOT NULL DEFAULT 0 COMMENT 是否置顶, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态0待审核 1已发布 2已驳回 3已删除, create_time datetime NOT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;回复表就简单一点CREATE TABLE reply ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL COMMENT 回复的帖子, user_id bigint(20) NOT NULL COMMENT 回复人, parent_id bigint(20) DEFAULT NULL COMMENT 父回复id支持楼中楼, content varchar(1000) NOT NULL COMMENT 回复内容, create_time datetime NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有一个设计决策值得展开讲回复表要不要做楼中楼我的建议是做。虽然会增加一点逻辑复杂度但收藏圈子里经常出现“追问”场景——比如有人发了一枚币的图下面有人回“边齿图有吗”然后又有人针对这个回复继续讨论。如果只做一层普通回复交流体验会非常差。楼中楼实现起来也不复杂parent_id为空表示顶层回复不为空表示这个回复挂在某条顶层回复下面前端用递归组件渲染就行。至于点赞表和收藏表都是典型的关联表结构几乎一样一个用户 id、一个帖子 id、一个创建时间加一个唯一索引保证不能重复点赞。这部分不需要额外介绍太多。4. 核心功能实现从登录鉴权到帖子审核数据库建好之后代码层的核心工作就是把这几个关键链路打通JWT 登录流程、帖子发布 - 审核 - 展示的完整链路、图片上传处理。我一个个说。4.1 JWT 登录流程的三个关键点Spring Security JWT 的完整配置涉及很多类我不打算贴全部代码网上满大街都是单说三个实操中容易出错或者容易被忽略的关键点。第一放行白名单一定要配全。登录接口、注册接口、前端静态资源、验证码接口这些都必须放进permitAll()里否则前端页面还没加载完就被重定向到 401 了。我当时的白名单配置长这样permit-urls: - /api/auth/login - /api/auth/register - /api/coin/list - /api/coin/detail/** - /api/post/list - /api/post/detail/** - /api/category/list - /doc.html - /webjars/** - /swagger-resources/** - /v2/api-docs注意我把前台列表和详情查询接口都放行了——因为未登录用户也应该能浏览社区内容这是交流类系统的正常需求。但如果某个接口既允许匿名访问又需要拿到当前登录用户的信息比如“这个帖子我有没有点过赞”就得在接口内部做“可选登录”的逻辑处理不能直接在 Security 配置层卡死。第二JWT 拦截器里要处理 token 过期和无效 token 两种情况不能统一返回同一个提示。我当时遇到过很尴尬的情况前端明明看 token 快过期了结果后端还返回“未登录”用户得手动退出再重新登录。后来我在拦截器里做了区分token 缺失返回“未登录”token 已过期返回“登录已过期请重新登录”token 非法返回“无效的凭证”。三个不同的响应码前端也好处理。第三密码加密不要手写 MD5。MD5 已经很不安全了而且没有任何盐值的话彩虹表一查就出来。用 Spring Security 自带的 BCryptPasswordEncoder每次调用encode()都会生成不同的哈希值安全性好而且类本身自带matches()方法校验原始密码和哈希是否匹配一行代码搞定。4.2 帖子发布与审核的完整链路这个链路是整个系统的核心业务流我画成一个动作序列来理解用户在前端填写帖子标题、正文、选择分类、上传图片。前端把数据 POST 到/api/post/create后端先解析 JWT 获取当前用户 id。后端校验标题不能为空、正文长度够、用户状态正常。生成帖子记录status默认是0 待审核。帖子进入管理员后台的“待审列表”。管理员点击审核通过帖子status变为1 已发布前台列表可见。管理员也可以驳回此时要填一个驳回原因例如“图片模糊请重新拍”。系统给发帖人生成一条站内通知“你的帖子《XXX》已通过审核”或“你的帖子被驳回了原因XXX”。这里有一个非常容易被初学者忽略的细节帖子一旦通过审核管理员再次编辑帖子后要不要重新审核我的处理方式是管理员直接编辑不触发重新审核管理员本身是可信的但是用户自己编辑已经通过审核的帖子时status自动回退到待审核状态。原因很简单如果用户在帖子通过审核后把内容改成违规内容就会绕过审核机制。必须让每次用户修改都重新走一遍审核流程。这个细节在我自己测试的时候发现的后来想想确实是个很大的漏洞。通知模块的实现也有心得。最简单的方式是在通知表里插一条记录用户查询未读通知时返回列表。但是我当时做了一个很小的优化通知完成之后通过 Spring Boot 自带的Async异步发送不阻塞主流程。这一步对用户体验提升是实打实的——发完帖子点击提交如果还要等通知写入完成整个请求响应会变慢异步的话用户点了发布马上就能看到“提交成功等待审核”的提示。4.3 图片上传文件路径的数据库存储策略钱币收藏系统的图片上传比普通系统要复杂一些因为涉及的图片场景太多用户头像、藏品多图、帖子多图、Banner 图。我在实现上做了统一处理。后端接收MultipartFile然后做这几件事校验文件类型只允许 jpg、png、webp 格式用文件扩展名和 MIME 类型双重校验。校验文件大小单张图片限制 5MB。太小的看不清钱币细节太大的浪费服务器带宽。生成存储路径按照日期分目录/uploads/20240605/xxx.jpg这样组织避免单目录下文件过多。文件名用 UUID 重命名不用原始文件名防止中文文件名和特殊字符在 URL 访问时出现编码问题。返回访问 URL数据库只存/uploads/20240605/uuid.jpg这种相对路径访问时通过配置的映射前缀拼成完整 URL。这里有个部署相关的坑。本地开发时可以直接把上传目录放在项目里的src/main/resources/static/uploads下但打成 jar 包部署后写到 jar 内部的路径是临时的重启就丢。所以生产环境必须把上传目录配置为外部绝对路径比如/data/coin/uploads然后在 Spring Boot 里通过配置把这个目录映射成静态资源。我在application.yml里的配置是这样upload: path: /data/coin/uploads spring: web: resources: static-locations: classpath:/static/,file:${upload.path}/这段配置要做的事情是Spring Boot 在请求访问/uploads/**路径时先去找 jar 包内的static/uploads找不到则去外部目录/data/coin/uploads找。把这个搞清楚之后本地开发和服务器部署的行为完全一致不会再出现“本地能看图打包上去图就 404”的问题。5. 前台社区部分列表排序、热点推荐与全文检索的取舍前台是用户直接面对的部分体验好不好全看细节。这一节讲讲社区列表、内容检索这两块的实现思路。5.1 列表排序的工程化实现帖子列表的排序规则我最终定为“综合热度排序”公式很朴素热度分 浏览量 * 0.3 点赞数 * 0.5 回复数 * 0.2 最近一周新发帖的加权加成不用太复杂的算法重点在于让刚发布的帖子有机会被看到又不至于因为初始数据太小被沉到底。我加了一个简单的时间衰减因子帖子发布时间如果在 48 小时以内热度分额外增加一个固定加分。这样新贴子有初始曝光同时讨论质量高的老帖也不会彻底沉没。SQL 层面的实现也很简单直接在查询时动态拼接排序字段choose when testsort hot ORDER BY is_top DESC, (view_count * 0.3 like_count * 0.5 reply_count * 0.2) DESC, create_time DESC /when when testsort new ORDER BY is_top DESC, create_time DESC /when when testsort like ORDER BY is_top DESC, like_count DESC, create_time DESC /when /choose分页用的就是 MyBatis-Plus 的Page对象前端用 Element UI 的el-pagination组件对接传递current和size参数即可。5.2 搜索功能别一上来就上 ElasticSearch做社区系统搜索功能绕不开。但你如果去搜教程会看到大量“SpringBoot 整合 ElasticSearch 实现搜索”的内容我很负责任地说这个项目体量完全不需要 ES。理由很简单ES 是一个分布式的搜索中间件启动要占用至少 1~2GB 内存部署和运维复杂度完全超出毕设或小型交流系统的需求。为了一个可能只有几百条测试数据的论坛帖子引入 ES属于杀鸡用牛刀答辩时反而会被问“你为什么选型这么重”我当时选的方案是 MySQL 的 LIKE 查询 FULLTEXT索引。具体来说就是SELECT * FROM post WHERE status 1 AND (title LIKE CONCAT(%, #{keyword}, %) OR content LIKE CONCAT(%, #{keyword}, %)) ORDER BY create_time DESC这个方案的问题是不能很好地对匹配度排序但数据量小的时候完全够用。如果后期帖子量上来了可以考虑 MySQL 的全文索引MATCH ... AGAINST也不需要引入额外的中间件。这样设计的好处是部署简单、配置零成本、答辩时还能说“我评估过 ES但当前业务量下 MySQL 全文检索更合适”这比什么都扛着上更能体现工程判断力。5.3 点赞/收藏的并发安全处理点赞功能看起来很 trivial——就是两个表的插入操作。但如果做不好会有两个 bug 很恶心一个是用户疯狂点按钮导致重复点赞记录一个是帖子点赞数和实际点赞记录对不上。第一个问题通过数据库唯一索引解决ALTER TABLE post_like ADD UNIQUE KEY uk_user_post (user_id, post_id);这样即使前端重复提交数据库层面也会拦截第二次插入。同时前端在用户点过赞之后禁用按钮双重保险。第二个问题就要讲究技巧了。不要每次都去count(*)然后再 UPDATE 回帖子表——数据量大时这就是个性能隐患。正确做法是把“更新帖子点赞数字段”和“插入点赞记录”放到同一个数据库事务里并且在更新帖子表时使用点赞数 点赞数 1这种原子操作Transactional public void like(Long postId, Long userId) { postLikeMapper.insert(new PostLike(postId, userId)); postMapper.increaseLikeCount(postId); }update idincreaseLikeCount UPDATE post SET like_count like_count 1 WHERE id #{id} /update注意like_count like_count 1这条 SQL 天然就是线程安全的不会出现两个用户同时点赞然后值只加了 1 的情况。取消点赞就是一个反向操作把like_count减回去。这一步是很多教程不会告诉你的细节但面试或者答辩时能主动说出来非常加分。6. 后台管理审核列表、用户治理和内容安全的落地姿势后台管理往往被轻视但实际上这部分工作量占比很大而且直接决定系统能不能上线用。我讲两个核心点审核列表的效率优化和内容安全的简单实现。6.1 审核列表联表查询与状态过滤管理员的待审帖子列表需要看的信息比前台多发帖人用户名、帖子内容摘要、所有图片、提交时间、当前状态。所以查询时要做post表和user表联表public IPagePostAdminVO selectAdminPage(Page? page, Param(status) Integer status) { return postMapper.selectAdminPage(page, status); }XML 里用LEFT JOIN把user表拉进来用status条件过滤。为了管理员审核方便图片字段要用逗号拆分成数组返回给前端前端用轮播图形式预览所有图片。这些都是很小的细节但直接影响审核效率——上传了五张图管理员如果只能一张一张点开看体验太差了。前端审核交互我做成了一行一个帖子的表格每一行右侧有“通过”“驳回”两个按钮驳回时弹出对话框填写理由。这个交互设计做完录入 50 条测试数据的实测体验就很顺了。6.2 内容安全简单 SensitiveWord 过滤作为一个带 UGC 内容的社区系统内容审核是必须考虑的。但这块做好做差差距很大——企业级做法是接阿里云内容安全、网易易盾这种 API通过机器学习识别色情、广告、辱骂内容。我调研过之后觉得这类服务在毕设项目里有两个问题一是需要实名认证开通服务二是需要付费有免费额度但很紧张。所以我的方案是两层第一层自建敏感词库 DFA 算法过滤。在发帖、回复、藏品描述等所有用户输入接口统一走一个SensitiveWordFilter工具类用 DFA确定性有限自动机算法做敏感词匹配——核心思想就是把敏感词构建成树形结构遍历用户输入时逐字符匹配时间复杂度 O(n)对短文本来说性能毫无压力。匹配到敏感词就替换成*同时记录触发日志。第二层管理员人工审核兜底。不管敏感词库里有多少词永远可能有漏网之鱼所以帖子发布后的状态机设计待审核 - 通过本身就是内容安全的最后一道防线。机器过滤 人工审核这个组合对这个体量的系统是合理且完整的。7. 前端搭建Vue 项目如何优雅地打包进 SpringBoot这个系统的前端我用的 Vue 2 Element UI Axios分成了前台用户端游客/普通用户访问和后台管理端管理员访问两个页面结构。关于前端本身我不想讲太多——组件化的页面搭建大家都差不多。这一节我想集中讲一个每次做这类项目都会有人踩坑的点Vue 项目怎么打包进 SpringBoot让前后端最后变成一个 jar 包直接跑起来。7.1 打包策略两种方案怎么选第一种方案是前后端彻底分离部署前端打包成dist后丢到 Nginx 里SpringBoot 单独跑通过反向代理转发 API 请求。这种方案适合真正上线的项目前后端独立迭代互不影响。第二种方案是把前端构建产物放到 SpringBoot 的静态目录里前端npm run build输出到后端的src/main/resources/static/目录下最终mvn package打成一个包含页面和接口的完整 jar。这种方案最省事部署时只要一个 Java 环境就能把整个系统跑起来。我推荐毕设或者小型项目用第二种方案。理由有三个一是部署环境要求最低——服务器只要有 JDK 就能跑不需要额外装 Nginx二是“一个 jar 包搞定所有”的演示效果特别好——拷到任何一台机器上java -jar就能访问完整系统三是省去了配置反向代理的各种麻烦。7.2 路由模式history 和 hash 的选择如果你选择了“前端打包进 SpringBoot”这条路那就有一个硬性约束Vue Router 必须用 hash 模式不能用 history 模式。原因很简单。history 模式的 URL 是http://localhost:8080/post/123浏览器会把这个地址当作后端接口路径去访问但 SpringBoot 后端没有对应的 Controller 做页面转发就会返回 404。hash 模式的 URL 是http://localhost:8080/#/post/123#后面的路径由前端 JS 自己解析不经过后端永远不会 404。这个坑我当年踩过明明页面代码没问题刷新一下就是白屏加 Whitelabel Error Page。后来把mode: history改成mode: hash整个世界清净了。7.3 Axios 请求路径的配置细节前后端集成之后Axios 的 baseURL 也要跟着调整。开发的时候前端跑在 8081 端口后端跑在 8080 端口跨域问题在 Vue CLI 的devServer.proxy里配置代理解决devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }打包之后前端页面和后端接口在同一个域名同一个端口下Axios 的 baseURL 就不用写完整地址了直接写/api就行。生产环境和开发环境的行为一致不需要切来切去。还有一个细节SpringBoot 默认的上下文路径是根路径/前端打包后的资源直接放 static 目录下就能访问。如果你的系统设置了server.servlet.context-path: /coin那前端资源访问路径和接口路径都要加上这个前缀整个复杂度就上去了。我的建议是不要设置 context-path保持默认根路径。7.4 后端打包时字节码版本和 JDK 版本必须对齐这个坑也值得单独提醒下。如果你本机用 JDK 17 开发但部署服务器只有 JDK 8那mvn package打出来的 jar 包在服务器上直接Unable to load class或者UnsupportedClassVersionError。所以 Maven 配置文件里的编译版本和管理端部署版本要保持一致。我的pom.xml里是这样的properties java.version1.8/java.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties本地开发环境即使安装的是 JDK 11 或 17只要 Maven 里配置了 1.8编译产物的字节码版本就是 Java 8 兼容的部署时 JDK 8 及以上都能跑。8. 部署与自查从本地跑通到服务器发布的完整清单最后一部分我把整个系统从开发机到服务器的“最后一公里”经验做一个系统梳理。这块内容很多人不重视但恰恰是答辩演示或实际上线时最容易翻车的地方。8.1 部署清单部署前先过一遍这个自查列表能省掉 80% 的现场事故数据库导入把sql目录下的建表 SQL 和初始化数据管理员账号、测试用户、分类数据在服务器 MySQL 里执行一遍。配置文件修改application-prod.yml里的数据库连接用户名、密码、IP 端口、上传路径、JWT 密钥。后端打包执行mvn clean package -DskipTests生成 jar 包。前端打包执行npm run build:prod确认dist内容已拷贝到后端src/main/resources/static下这一步要在第 3 步之前做。启动验证java -jar xxx.jar --spring.profiles.activeprod看日志是否会报数据库连接错误。接口冒烟测试用浏览器或者 Postman 依次访问登录、获取文章列表、上传图片几个核心接口。图片路径验证上传一张图片访问返回的 URL确认能够打开。XXL-Job 之类的定时任务如果用了 SpringBoot 自带的Scheduled确认是否正确配置了执行时间。我讲一个真实经历有一次我打包完在本地跑得好好的上传到服务器就是访问不了页面。排查了半天发现是static目录下前端打包后的index.html没被 copy 进去——因为 Maven 有时候会忽略空目录或者构建顺序不对。这个问题的解决办法是在pom.xml的build节点里加一个resource配置强制把src/main/resources/static目录打进去build resources resource directorysrc/main/resources/directory filteringfalse/filtering /resource /resources /build8.2 部署后的运维小技巧服务器端图省事的话可以用nohup java -jar xxx.jar log.log 21 直接启动但这样进程管理太原始了。我自己习惯用 Systemd 服务管理[Unit] DescriptionCoin Collect System Afternetwork.target [Service] Userroot WorkingDirectory/data/coin ExecStart/usr/local/jdk/bin/java -jar /data/coin/coin-system.jar --spring.profiles.activeprod SuccessExitStatus143 Restartalways [Install] WantedBymulti-user.target用 Systemd 的好处是服务崩了自动重启开机自启日志统一由journalctl -u coin管理线上排错特别方便。数据库这块有个操作要提醒——备份。社区系统数据是核心资产我写了一个简单的 Shell 脚本放在 crontab 里每天凌晨全量备份#!/bin/bash mysqldump -uroot -ppassword coin_db /data/backup/coin_$(date %Y%m%d).sql find /data/backup -name *.sql -mtime 7 -exec rm {} \;每天凌晨自动备份一次保留最近 7 天这个脚本虽然简单但有了它之后我晚上睡觉都踏实多了。9. 复盘总结这套系统的可扩展方向一路做下来这个系统已经是一个真正能跑起来的完整项目了。它的价值不仅在于“钱币收藏”这个具体领域更在于它把一个典型社区型业务系统的完整闭环做通了用户注册登录、内容发布、审核机制、互动点赞收藏、后台管理、文件存储、前后端打包部署。这套逻辑往后套到任何垂直领域的交流平台集邮、手办、模型、钓鱼、宠物都成立核心代码几乎不用改所以如果你拿这个项目去面试或者做基础二开收益是相当大的。从可扩展性角度说还有几个可以继续深化的方向留给有精力的朋友去尝试推荐算法可以在“热度排序”基础上加一个协同过滤或者基于用户行为的简单推荐比如“和你口味相似的人也在看”。实时消息推送用 WebSocket 实现在线聊天或者私信功能把“交流”属性延伸得更深。图片处理增强比如加水印防盗窃图、生成缩略图提高加载速度、用 AI 辅助鉴定钱币真伪这个目前在泉友圈特别火。对接微信小程序这套后端 API 设计成 restful 风格天然可以复用到小程序端把 PC 社区延伸成移动端产品。我个人觉得做项目和学习技术一样最高效的路从来不是拿着别人的代码照抄而是搞懂每一个设计决策背后的“为什么”——为什么表要这样建为什么状态要这样流转为什么要选这个组件而不是那个组件。当你把这些“为什么”都能讲清楚的时候这个项目才真正是你的了。希望这篇复盘能给你一些参考哪怕只有一个点让你少踩一个坑今晚的码就没有白敲。