做汽车资讯类网站很多同学的第一反应是“把前台页面做得好看点、轮播图多放几张就完事了”。但真等项目落地你就会发现最花时间和精力的地方全在你看不见的后台管理端。内容怎么发布、品牌车型数据谁来维护、评论要不要审核、不同角色能看什么页面、每天有多少访问量这一整条链路才是项目的真正核心。我自己完整做过一个基于SpringBootVue的汽车资讯网站管理系统从需求梳理、数据库设计到后端接口开发、前端页面联调再到部署验收把整条流程走通之后攒了不少经验。这篇文章就把这套系统的设计与实现从头到尾拆解一遍包含业务模块划分、数据库表结构、接口规划、权限控制、前端路由与组件复用、联调期典型Bug以及最后交付验收时的检查清单。项目用的是JavaMySQLMyBatis的标准技术栈后端SpringBoot、前端Vue属于目前最主流也最好上手的企业级全栈组合。适合正在准备毕业设计、做课程训练项目或者想从“会写接口”进阶到“能独立交付一个完整系统”的同学参考。1. 先想清楚汽车资讯网站真正需要管理什么1.1 前台页面只是冰山一角任何一个资讯类网站用户能看到的东西其实非常有限首页的轮播图、资讯列表、资讯详情、品牌车型展示、二手车信息列表再加上登录注册和个人中心撑死了十来张页面。但支撑这些页面正常运转的后台才是整个系统的工作重心。我当时在做需求分析时把后台管理端需要的能力列了一张表大致是这样的资讯管理编辑发布汽车资讯、设置置顶、上下架、删除支持多级分类。品牌与车型管理维护汽车品牌列表品牌下面挂具体车型包括图片、级别、价格区间、能源类型等参数。车辆信息管理管理车主发布的出售信息或二手车辆信息后台可以进行审核和推荐。评论管理用户对资讯的评论需要审核防止垃圾信息和争议内容出现。轮播图管理首页Banner的增删改、排序、上下线。用户与权限管理区分管理员和普通用户两种角色管理员只能由后台创建普通用户通过前台注册。数据统计在首页Dashboard里展示关键数据比如今日新增资讯数、评论数、访问量、品牌数量等。这些模块每一个单独拎出来都不复杂但组合在一起就需要一套清晰的数据模型和接口约定否则很容易在开发到一半的时候发现表结构设计不合理回头改得痛不欲生。1.2 用户角色与权限边界这个项目里我做了两种角色管理员拥有后台所有功能的操作权限包括对资讯、品牌、车型、评论、用户的管理。普通用户只在前台使用可以浏览资讯、查看车型、发布车辆出售信息、发表评论但没有任何后台管理页面的访问权。权限控制如果做得太复杂反而会增加学习成本。这个项目的规模决定了用“角色前端路由拦截后端接口鉴权”的双重控制就够了前端通过路由守卫控制页面访问后端通过拦截器校验每个请求的权限身份。后面我会单独说这块的实现细节。另外还要提一个很容易被忽略的需求内容审核。汽车资讯网站的内容有很强的行业属性涉及新车参数、官方售价等数据如果让用户随意发布很容易出现信息错误。所以当时我把“用户提交的信息”和“正式展示的信息”分开处理用户发布的内容先进入待审核状态管理员确认无误后再正式上线。这个设计在后来的演示和答辩中反而成了加分项因为能体现你对内容安全的考虑。2. 技术栈选型这套组合背后的真实理由2.1 后端为什么选 SpringBoot MyBatis先说结论对于这种以CRUD为主的管理系统SpringBoot MyBatis 基本上是最稳妥的选择没有之一。SpringBoot 解决了 Spring 家族配置繁琐的问题内嵌 Tomcat一个 main 方法就能启动非常适合快速搭建工程骨架。而且它的生态太成熟了分页插件、JWT 工具、文件上传、参数校验各种场景都有现成的集成方案不用自己造轮子。MyBatis 在这类系统里的优势是“SQL 可控”。汽车资讯系统的查询场景经常伴随多表关联资讯表关联分类表、车型表关联品牌表、车辆信息表关联用户表。用 MyBatis 手写 SQL 虽然比 JPA 多敲几行代码但每一条 SQL 的性能和结果集都在你掌控里尤其是复杂统计查询写起来非常直观。比如统计每个品牌下的车型数量一条GROUP BY就能搞定JPA 反而要琢磨半天怎么映射。如果你在纠结 SpringData JPA 和 MyBatis 怎么选我的建议很简单数据模型简单、实体关系不多用 JPA 省事但只要涉及到多表关联查询、统计分析、报表汇总这类需求老老实实选 MyBatis后期改 SQL 的成本低得多。2.2 前端为什么选 VueVue 在这类后台管理系统里的优势是“渐进式、上手快、中文资料多”。管理端本质上是大量表格页、表单页、弹窗和配置页面的集合Vue 的单文件组件、双向绑定和组件化开发能让这些页面的开发效率提升非常明显。我在这个项目里还引入了 Element UI 这类现成的组件库表格、表单、弹窗、树形控件、日期选择器、消息提示全都直接拿来用页面风格统一开发速度也快。相比从零手写一套管理后台的 UI 框架这至少省了一半的时间。考虑到系统的使用场景是 PC 端管理后台我建议直接用 Vue 2 搭配 Element UI而不是一上来就追 Vue 3 Element Plus。原因很现实Vue 2 的社区资料和踩坑案例最全遇到问题搜索引擎一搜就有答案对新手极其友好。当然如果你本身已经熟练 Vue 3 的组合式 API直接用 Vue 3 也没问题整体架构思路完全一致。2.3 数据库与部署方案的现实考量数据库选了 MySQL 5.7一个是它足够稳定另一个是这个版本在很多服务器环境里可以直接安装不用折腾对后面的部署上线很友好。字符集当时统一用了 utf8mb4一开始没注意这个细节的话以后存表情符号或者特殊字符会直接报错这一点最好是建库时就一步到位。部署方案用的是比较传统的方式后端打 jar 包跑在服务器上前端编译后的静态资源交给 Nginx 托管MySQL 单独部署。图片文件因为项目规模不大直接存服务器的本地磁盘通过一个upload接口返回访问路径。这个方案虽然朴素但足够应对课程设计、毕设演示和中小规模真实使用的场景。如果你想把系统做成开源项目或者正式商用后续再接入云存储也不迟。3. 数据库设计核心表结构与字段规划3.1 六张核心表撑起整个业务整个系统的数据模型我最终收敛成了六张核心表用户表、资讯分类表、汽车资讯表、品牌表、车型表、评论表。再加上车辆信息表和轮播图表一共八张表但真正参与核心业务流转的就是前面六张。先看用户表的设计CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT MD5加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, role TINYINT NOT NULL DEFAULT 1 COMMENT 角色0管理员 1普通用户, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;字段设计上几个要点密码一定不要明文存储我当时用的是 MD5 加盐的方式虽然现在更推荐 BCrypt但演示级别的项目用 MD5 也能交代过去前提是要在文档里明确说清楚这是简化处理生产环境必须换成 BCrypt。用户名加唯一索引避免重复注册。角色和状态都用TINYINT代替字符串既省空间又方便判断。品牌表和车型表是一对多的关系这是汽车资讯系统的核心数据骨架CREATE TABLE car_brand ( id INT NOT NULL AUTO_INCREMENT, brand_name VARCHAR(50) NOT NULL COMMENT 品牌名称, brand_logo VARCHAR(255) DEFAULT NULL COMMENT 品牌Logo, country VARCHAR(50) DEFAULT NULL COMMENT 品牌国家, description TEXT COMMENT 品牌介绍, sort_order INT DEFAULT 0 COMMENT 排序权重, status TINYINT DEFAULT 1 COMMENT 1展示 0隐藏, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT汽车品牌表; CREATE TABLE car_model ( id INT NOT NULL AUTO_INCREMENT, brand_id INT NOT NULL COMMENT 所属品牌ID, model_name VARCHAR(100) NOT NULL COMMENT 车型名称, level VARCHAR(20) DEFAULT NULL COMMENT 级别紧凑型/中型/SUV等, price_range VARCHAR(50) DEFAULT NULL COMMENT 价格区间, fuel_type VARCHAR(20) DEFAULT NULL COMMENT 能源类型汽油/纯电/混动, image VARCHAR(255) DEFAULT NULL COMMENT 车型图片, params TEXT COMMENT 核心参数JSON格式存储, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_brand_id (brand_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车型表;这里有个值得说的细节车型的“级别、能源类型、价格区间”这类字段我没有另外建字典表而是直接存字符串。因为汽车资讯网站管理端的维护频率不算高就算要改直接改一条数据也很快。但如果你做的系统分类维度很多、后期可能要扩展那还是拆成字典表更规范。这个取舍要依据实际业务量来定。3.2 外键约束建还是不建很多同学建表的时候习惯把外键加上觉得这样数据更完整。但在这个项目里我对表与表之间的关联全部采用了“逻辑外键”的方式也就是不建物理外键约束只在查询时通过JOIN关联。原因有两条。第一MyBatis 在多表操作时物理外键会影响批量删除和更新的灵活性比如你要删一个品牌品牌下面有车型、车型下面有资讯物理外键会导致删不下去还得先处理子表数据而逻辑外键只需要在删除前检查一下有没有关联数据即可。第二管理系统的数据量不大外键带来的完整性保障其实没那么必要反而增加维护成本。但“不建物理外键”不代表“不检查关联数据”。我在删除品牌接口里会先查车型表有没有引用这个品牌的记录有的话拒绝删除并提示“该品牌下存在车型无法删除”。这个逻辑写在后端 Service 层比数据库外键更好控制也能返回更友好的提示信息。3.3 资讯表的内容字段设计资讯表是核心中的核心字段设计上有几个坑值得注意CREATE TABLE car_news ( id INT NOT NULL AUTO_INCREMENT, category_id INT NOT NULL COMMENT 分类ID, title VARCHAR(200) NOT NULL COMMENT 标题, cover_image VARCHAR(255) DEFAULT NULL COMMENT 封面图, summary VARCHAR(500) DEFAULT NULL COMMENT 摘要, content LONGTEXT COMMENT 正文内容HTML格式, author VARCHAR(50) DEFAULT NULL COMMENT 作者, source VARCHAR(100) DEFAULT NULL COMMENT 来源, view_count INT DEFAULT 0 COMMENT 浏览量, like_count INT DEFAULT 0 COMMENT 点赞数, is_top TINYINT DEFAULT 0 COMMENT 是否置顶, status TINYINT DEFAULT 0 COMMENT 状态0草稿 1已发布 2下架, publish_time DATETIME DEFAULT NULL COMMENT 发布时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_id (category_id), KEY idx_status_publish (status, publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT汽车资讯表;内容字段我用了LONGTEXT专门用来存富文本编辑器生成的 HTML。前端用v-html直接渲染这样可以完整保留编辑时的排版效果。但这也带来一个安全风险XSS 注入。PHP 那边这句老话同样适用于 Javav-html渲染用户输入前一定要做过滤。我的处理方式是后端在保存资讯时把script标签、事件属性等危险内容做一次性清理同时在前端渲染时对内容做转义处理双重保险。另外注意view_count和like_count这两个统计字段我直接冗余在资讯表里浏览一次加一点赞时做唯一约束防止刷赞。如果数据量很大这种做法的并发更新会成为瓶颈但对于资讯管理项目完全够用而且查询时不用联表性能反而更好。4. 后端实现的几个关键设计4.1 接口规划RESTful 风格的取舍整个系统我按资源划分了接口每个业务模块对应一组 RESTful 接口。以资讯为例GET /api/news/list?pageNum1pageSize10分页查询资讯列表支持标题关键字、分类、状态筛选。GET /api/news/{id}资讯详情同时浏览量加一。POST /api/news新增资讯。PUT /api/news/{id}修改资讯。DELETE /api/news/{id}删除资讯。PUT /api/news/{id}/status上架、下架、置顶操作。用 RESTful 风格的好处是接口语义清晰前端对接时看一眼路径就知道是做什么的。但我没有过度追求 RESTful 的纯度比如“上架/下架”这种操作理论上应该用PATCH修改状态字段但为了前端调用方便我直接定义了一个status接口用 PUT 传status参数简单直接。接口设计的目的是让前后端协作高效而不是为了风格纯洁这一点我一直很坚持。4.2 登录认证与权限控制认证方案我选了 JWT而不是 Session。原因很简单前端是 Vue后端是纯接口服务用 JWT 可以把登录状态完全交给前端管理后端接口做到无状态以后想扩展移动端或者小程序端同一套接口直接复用。具体流程是这样的用户登录成功后后端生成一个包含用户ID和角色信息的 Token 返回给前端前端存储到localStorage之后每次请求在请求头带上token字段。后端写了一个拦截器对除了登录注册和前台公开接口之外的所有路径进行校验public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(token); if (!StringUtils.hasText(token)) { throw new BusinessException(401, 未登录请先登录); } // 解析token校验签名和有效期 Claims claims JwtUtil.parseToken(token); if (claims null) { throw new BusinessException(401, 登录状态已过期请重新登录); } // 将用户信息存入request方便后续获取当前登录用户 request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }角色鉴权我用了自定义注解RequireRole在需要管理员权限的接口上标注然后在拦截器里校验role字段。比如资讯新增接口RequireRole(admin) PostMapping(/api/news) public Result addNews(RequestBody News news) { newsService.addNews(news); return Result.success(); }这里要说一个容易被忽略的点前端路由守卫只能挡住“点到那个路由”的访问防不住“直接调接口”。比如普通用户虽然进不了管理页面但他完全可以用浏览器控制台手动发一个新增资讯的请求。所以后端接口鉴权才是安全底线前端路由守卫只是体验层面的优化。这个思路在答辩或技术评审时提出来会显得你确实想清楚了安全问题。4.3 事务、校验与全局异常处理后端开发最容易出的问题是“一个操作涉及多张表中间某一步失败了数据变成一半成功一半失败”。比如发布资讯时需要同时插入资讯表和更新分类的文章数量如果第二步失败资讯表就多了一条脏数据。解决办法很简单在 Service 层的方法上加Transactional注解让这两步处于同一个事务里任何一步抛异常都会整体回滚。我当时排查最久的一个Bug就是因为删评论时既要删评论又要更新资讯的评论计数忘了加事务导致评论删了计数没更新。这类问题全部可以通过事务解决写代码的时候就要养成习惯只要一个方法里写了超过一条增删改SQL考虑加事务。参数校验方面我在实体类的字段上加了NotBlank、NotNull这类注解然后用Validated在 Controller 入参上触发校验。但数据库层面也要有兜底非空字段一律设为NOT NULL长度尽量和业务匹配防止前端传超长数据把表搞坏。全局异常处理统一用RestControllerAdvice实现整体的设计是业务异常返回固定的错误码和提示信息未知异常返回“系统繁忙请稍后再试”同时记录完整异常堆栈到日志文件。这样前端拿到的是一个结构统一的响应体处理逻辑简单后端排查问题也能直接从日志定位。5. 前端 Vue 落地过程中的实战细节5.1 路由设计与权限拦截Vue 前端的路由我分成了两大部分前台页面和管理后台页面。前台页面包括首页、资讯列表、资讯详情、品牌车型、关于我们等登录用户还可以进入个人中心发布车辆信息管理后台页面则包含仪表盘、各类管理列表、编辑页面。管理后台我统一挂在/admin路径下通过一个简单的路由前守卫控制访问router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path.startsWith(/admin)) { if (!token) { next(/login) return } const role localStorage.getItem(role) if (role ! admin) { next(/) return } } next() })这段代码虽然简短但它承担了“未登录用户不能直接输入地址访问管理后台”的基础拦截。要注意的是这只做页面层拦截真正的数据安全必须靠后端接口的 JWT 鉴权来兜底两者配合使用缺一不可。5.2 组件复用管理后台的开发效率关键管理后台的页面形态高度相似——几乎每个模块都是“搜索区 表格 分页 新增/编辑弹窗”这四件套。如果每个页面都从零写一遍代码量会翻好几倍所以我当时花了点时间抽了几个公共组件。最核心的是PageTable组件它封装了搜索表单渲染、表格列配置、分页逻辑、操作按钮插槽。使用时只需要配置列定义和搜索项就能渲染出一个完整的列表页page-table reftableRef api/api/news/list :search-itemssearchItems :columnscolumns handle-edithandleEdit handle-deletehandleDelete /这个决策带来的收益非常明显后期新增一个运营数据模块时我只需要新增一个路由页面配置好列和搜索项前后花了不到一小时。代码不是写得越多越好而是把重复的部分抽象出来让每个模块只写差异化的逻辑。5.3 富文本编辑和图片上传的坑资讯编辑页需要用富文本编辑器。我当时在选型上对比了几个方案最终选了社区活跃度高、中文文档全的 wangEditor基于它封装了一个公共的富文本组件和图片上传接口打通。图片上传是这里最典型的一个坑。编辑器默认上传图片的方式是把图片 base64 编码直接存入内容如果图片多、像素高整篇资讯的 HTML 内容可能轻松超过几兆字节不仅数据库存储压力大前端渲染也会变卡。正确做法是配置编辑器的customUpload回调把图片文件通过接口上传到服务器拿到返回的 URL 之后再插入到内容里uploadImg: async function (files, insertFn) { const formData new FormData() formData.append(file, files[0]) const res await uploadApi(formData) insertFn(res.data.url) }这样保存到数据库的是一串图片地址而不是几十万字符的 base64 字符串。教训很直接凡是涉及文件上传的功能第一原则就是不要把文件内容存数据库存路径再让 Web 服务器对外提供静态访问。5.4 状态管理什么时候用 Vuex这个项目里我用了 Vuex但只用了一个模块保存用户登录信息。有些人可能会问管理后台这么多页面为什么不把列表数据也放 Vuex 里我的答案是不建议。列表数据的特点是时效性强、每次进页面都要刷新放 Vuex 反而会造成旧数据残留还得想怎么清理。Vuex 的定位是跨组件共享的、低频变化的数据比如登录用户信息、系统配置、购物车数据这类而列表、详情这类接口数据每次进入页面用 Axios 请求就行配合请求拦截器统一处理 Token 携带和错误提示。当时我见过很多半吊子项目把接口数据一股脑塞进 Vuex把状态管理变成一个大杂烩调试起来非常痛苦。Vuex 要克制使用权限数据和用户信息放进去其余的留在组件内部这个边界要在团队里作为约定明确下来。6. 联调和交付阶段踩过的典型问题6.1 跨域问题与前端请求配置前后端分离开发时最经典的bug就是跨域。前端跑在localhost:8080后端跑在localhost:9090前端axios发请求直接network error这个状态让很多新手崩溃。我当时的前端请求配置思路是在 Vue 项目的根目录创建.env.development环境变量文件设置接口基础地址为后端地址然后axios实例统一读取这个基础地址同时在请求拦截器里加上 Token 和统一的错误提示const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.token token } return config }) service.interceptors.response.use( res res.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )后端这边我在 SpringBoot 里写了一个跨域配置类允许本地开发环境的地址访问。有两个细节容易踩坑一是allowedMethods一定要加上 OPTIONS否则浏览器预检请求会直接失败二是如果需要携带自定义请求头要记得allowedHeaders不要只写死某一项用通配符或者显式列全。开发环境解决之后生产环境反而变简单了前端打包后的静态文件放在 Nginx 里后端接口通过 Nginx 反向代理到 jar 包的端口二者同源浏览器里就看不到跨域问题了。所以如果你部署上线后还报跨域想一下是不是用localhost:8080直接请求了服务器接口把前端文件放到 Nginx 管理就能解决。6.2 分页查询的翻车现场分页是这个系统里使用频率最高的功能也是最容易出问题的。当时用的是 MyBatis 的分页插件 PageHelper平时单表查询一切正常但一旦涉及多表关联就出过两次问题。第一次是 COUNT 查询优化的问题。PageHelper 会自动生成一条 count 语句来统计总数但如果你的查询里用了复杂的LEFT JOIN或者条件里有GROUP BY生成的 count 语句偶尔会不准确。我当时在车型列表按品牌筛选时总数就一直比实际少了几条。排查方式是把控制台打印的 SQL 拿出来单独执行对比 count 结果最后发现是关联条件里有隐藏的重复数据。解决办法是在 mapper 里手写专用的 count 查询而不是依赖插件生成的语句。第二次是分页参数和排序冲突。给表格加排序功能时直接把排序列名拼进了ORDER BY结果传了“更新时间”这类字段名进来SQL 语法报错。这个问题本质上是“排序字段没有白名单校验”。我后来的处理方式是设置一个允许排序的字段集合前端传过来的字段必须先在这个集合里否则强制走默认排序。这个方案同样适用于其他所有动态拼接 SQL 的场景能有效避免注入风险。6.3 时间字段显示错乱时间显示是前后端联调里最不起眼却最容易翻车的细节。数据库里存的create_time明明是2024-01-15 09:30:00前端表格里却显示成2024-01-15T09:30:00或者直接变成乱码。原因出在 Java 里的 LocalDateTime 默认序列化格式是 ISO 标准和前端date-fns或者组件库期望的格式不一致。我当时的解决方案是两步先配置 Jackson 的全局日期格式化让所有接口返回的时间统一为yyyy-MM-dd HH:mm:ss再在前端项目里封装一个公用的时间格式化函数在表格组件里统一调用。这样就把时间格式问题收敛到了初始化的配置阶段后面几乎没再因为这个改过代码。6.4 删除操作的连带数据清理删除操作看似简单实际处处是坑。项目里有几个外键关联场景删除品牌时要处理该品牌下的车型及其资讯删除资讯时要处理评论删除用户时要处理用户发布的车辆信息。我当时先是只删主表数据结果显示页面不断报错——评论列表里出现了“资讯不存在”的空引用。后来我花了半天时间做了一轮彻底的“删除连带梳理”才把删除逻辑理顺。结论是删除操作必须考虑关联数据的处理策略一般就三种——级联删除把关联数据也删掉、置空引用把外键置为null比如评论里的资讯删了就显示“已删除”、限制删除有子数据就不让删。我在项目里按模块做了不同选择删除资讯时级联删除评论删除品牌时限制删除必须先把品牌下的车型清理掉删除用户时保留其历史评论但标记为已注销。每种选择都要在删除前弹出二次确认让操作者明确知道后果并且删除操作全部使用逻辑删除字段deleted而不是物理删除万一误删还能找回。7. 部署上线与最终验收7.1 从本地到服务器的部署步骤项目写完后部署上线是绕不开的一步。整个过程可以拆成几个大步骤每步都不复杂但要按顺序来准备一台服务器安装 JDK 8、MySQL 5.7、Nginx。这没什么技术难度但环境变量一定要配好我见过很多次启动报错都是因为JAVA_HOME没配。在 MySQL 里创建数据库导入建表 SQL 和初始化数据账号密码要和配置文件保持一致。后端项目打包用 Maven 执行mvn clean package -Dmaven.test.skiptrue生成 jar 包上传到服务器用nohup java -jar xxx.jar --server.port9090 app.log 21 启动。前端npm run build生成dist目录上传到 Nginx 的 html 目录配置 Nginx 将/api/路径反向代理到http://localhost:9090。验证先测试后端接口是否能通再测试前端页面登录流程最后测试文件上传和图片访问。部署中有一个经验很值得分享不要等到项目全写完才开始准备部署最好在开发环境跑通 “前后端分离 → Nginx 反向代理 → 后端接口” 这条链路后再继续开发。提前把部署链路打通之后每次更新都只是替换 jar 包和前端 dist 目录的事。7.2 交付验收的检查清单系统开发完毕在写文档和准备答辩或者交付之前我建议你自己先按这个清单过一遍能发现不少平时开发中忽略的问题非法输入测试标题为空、密码过短、分页页码传负数系统是否有友好提示而不是直接抛 500。越权访问测试普通用户登录后直接访问管理员页面的路由是否被拦截直接调管理员接口是否被后端拒绝。空数据处理列表页没有数据时是否有“暂无数据”占位详情页访问不存在的 ID是否返回明确提示。数据一致性删除品牌、删除资讯后关联数据是否处理干净。安全底线密码是否加密存储、接口是否都有鉴权、上传的文件是否有格式限制。基础体验刷新页面后登录状态是否保持、退出登录后是否清掉了本地 Token、浏览器回退是否会出现已删页面。我自己在检查时最容易翻车的就是越权访问测试前端的路由拦截和后端的接口鉴权只要有一处没覆盖到就会出现“页面进不去但接口能调通”的尴尬情况。所以别怕麻烦每条都实际跑一遍跑完心里才有底。写在最后的一些体会整套系统从需求分析到部署完成前后花了大约三周多的时间。回看整个过程让我觉得最有价值的并不是“用了多新的技术”而是把一套完整业务从零到一的梳理和执行能力——先想清楚业务到底要做什么再决定表结构怎么设计然后才是写代码。这中间我反复调整过几次设计最大的感悟是表结构设计阶段的每一个草率决定都会在开发后期放大成好几倍的返工成本。比如“用户发布信息和正式内容是否分开存储”这个决定我是在开发到一半时才想明白要做审核流程的导致车辆信息表临时加了一个status字段连带前端表格、后端查询逻辑都跟着改了一遍。如果能重来一次我会在写第一行代码之前把业务流程完整画一遍把每个状态流转和关联关系都想清楚再动工。这个系统的架构和技术方案并不前沿但它足够完整、足够落地。如果你也准备做类似的资讯管理类项目把这套表结构和权限设计的思路吃透换成其他行业领域的内容也完全可以直接迁移。项目最大的对手从来不是技术难度而是你有没有把一个简单系统彻彻底底做完整的耐心。
阅读完成 · 觉得有帮助?