多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot+Vue+MySQL网站信息管理系统源码实测:从建库到登录运行指南

SpringBoot+Vue+MySQL网站信息管理系统源码实测:从建库到登录运行指南 说起来挺奇怪的网上打着“SpringBoot Vue MySQL 管理系统源码”旗号的项目不少但真正能让你在半小时内跑起来的其实没几个。要么是数据库脚本缺字段要么是前端依赖版本冲突要么是配置文件里写死了别人的本地路径。我最近拿到一套“网站信息管理系统源码”后端是 SpringBoot前端是 Vue数据库用的 MySQL标题里明确写了“可直接运行”。本着验证的心态我把整套流程走了一遍从建库到后台登录再到发布第一条文章数据整个过程确实顺。这篇文章就把这套系统的功能拆解、核心实现逻辑和完整运行步骤写清楚给正准备做类似管理系统、或者在为课程设计找参考项目的朋友一份真实可用的操作底稿。1. 这个“网站信息管理系统”到底管什么——项目全景拆解1.1 核心功能模块一览这套系统说白了就是一套通用内容管理后台核心是管“网站上要展示的信息”。我把它拆开看了一遍功能模块做得比较克制没有堆一堆用不上的东西每个模块都能对应到实际业务场景。模块主要功能典型使用场景栏目管理树形栏目结构、排序、启停用官网导航“关于我们”“产品中心”“新闻动态”等分类维护文章管理发布、编辑、删除文章置顶、上下线、按栏目筛选新闻动态、公告通知、行业资讯的日常更新轮播图管理首页Banner图片、跳转链接、排序官网首页焦点图替换友情链接管理合作伙伴名称、URL、Logo、排序站底友情链接维护系统用户管理后台账号增删改、角色区分、状态禁用给运营人员开子账号站点配置站点名称、Logo、ICP备案号、统计代码等键值对配置改网站标题、底部版权信息从这个功能列表能看出来这套系统的定位很清晰它不是一个功能堆砌的“大而全”平台而是一个给中小型官网、企业站、课程设计项目做内容维护的通用后台。如果你需要往里面加业务比如商品管理、订单管理架构上是有扩展空间的但那是二次开发的事。1.2 技术选型为什么是经典的老三样而不是追新我看到有不少人在问“为什么不用 Spring Cloud”“为什么不用 Vue 3 Vite”这里我多说两句。很多管理类系统尤其是信息管理、内容发布这类业务最大的诉求其实不是高并发、分布式而是稳定、可维护、资料多。SpringBoot 加 MySQL 的组合在国内的 Java 开发者里存量最大遇到问题一搜就有答案Vue 2 和 Element UI 的搭配经过大量生产项目验证组件行为稳定文档齐全。这套源码没有整那些花活反而好落地。从二次开发角度看SpringBoot 的自动装配大幅减少了配置时间只需要关注 Controller、Service、Mapper 三层逻辑Vue 2 的单文件组件和 Element UI 的现成表格、表单、弹窗组件能让后台界面的开发速度提升一个量级MySQL 作为关系型数据库对于后台管理系统这种“增删改查 统计筛选”的场景是天然契合的。技术选型不是越新越好而是越匹配场景越好。1.3 拿到源码后的第一件事先看目录结构别急着敲命令很多人下载源码后第一反应就是打开 IDE 直接跑结果跑出一个大花脸报错。我的习惯是先花十分钟看看目录结构心里有数了再动手。这套源码的根目录一般分两个子工程一个backendSpringBoot一个frontendVue。后端包结构是典型的controller - service - mapper - entity四层com.site.admin ├── config // 跨域、拦截器、资源映射等配置类 ├── controller // 接口入口 ├── entity // 数据库实体类 ├── mapper // MyBatis-Plus 数据访问层 ├── service // 业务逻辑层 ├── common // 统一响应结果、异常处理 └── util // JWT、MD5 等工具类前端结构是 Vue 标准的工程组织方式src ├── api // 按模块封装的接口请求 ├── router // 路由配置 ├── store // 用户状态管理 ├── views // 页面组件login/article/category/banner/user/setting ├── layout // 后台整体布局侧边栏顶栏 └── utils // axios 封装、token 处理等目录清爽的项目通常代码风格也不会差到哪去。看完结构之后再去对应application.yml、package.json、数据库脚本这三处关键的配置就可以进入正式运行环节了。2. 后端 SpringBoot 核心实现——接口、鉴权与文件上传2.1 基于 Controller-Service-Mapper 三层结构的接口设计后端整体是标准的业务三层写法Controller 不写业务、Service 不写 SQL、Mapper 只做数据访问。这种分层的好处是改动某一层不影响其他层比如你要把 MyBatis-Plus 换成 JPA只需要重写 Mapper 层和实体注解Controller 和 Service 的接口契约都不用动。核心接口我列一下你拿到源码后可以直接在浏览器里验证需要先启动后端请求方式路径功能说明POST/api/auth/login管理员登录返回 tokenGET/api/auth/info获取当前登录用户信息GET/api/article/list分页查询文章列表支持关键词、栏目、状态筛选POST/api/article新增文章PUT/api/article/{id}编辑文章DELETE/api/article/{id}删除文章逻辑删除GET/api/category/tree栏目树形列表POST/api/upload上传图片返回访问 URLGET/api/site/config获取站点配置这里有个细节值得留意接口统一以/api开头这是后端和前端的一层“约定”。开发环境前端通过代理把/api转发到localhost:8080生产环境 Nginx 也只要转发这一个路径就行不用每个接口单独配代理。统一响应体也做得规整所有接口返回的都是同一个结构public class ResultT { private Integer code; // 200 成功500 失败401 未登录 private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }前端拿响应的时候只判断code就行不需要关心 HTTP 状态码怎么变。这种约定在前后端分离项目里非常重要能在很大程度上减少联调时你问我答的沟通成本。2.2 JWT 登录鉴权是怎么串起来的后台管理系统第一个要解决的问题就是“谁可以进来看、谁可以操作”。这套系统用的是 JWTJSON Web Token方案而不是传统的 Session。为什么是 JWT关键原因是前后端分离。前端是独立部署的页面后端是独立服务如果用 Session就得考虑 Session 共享、跨域携带 Cookie 等一系列问题而 JWT 把用户凭证信息加密后直接发给前端前端每次请求在请求头带上这个 token 即可后端既不存状态也不依赖 Cookie天然适合这种架构。登录流程简单说就是三步// 第一步校验用户名密码 User user userService.login(username, md5(password)); if (user null) { return Result.fail(用户名或密码错误); } // 第二步生成 JWT token String token Jwts.builder() .setSubject(user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, jwtSecret) .compact(); // 第三步返回给前端 return Result.ok(token);加密方式用的是经典的两层组合密码存库前做 MD5登录成功后的 token 用 JWT 签名。要注意的是密码 MD5 在正式生产环境里已经算不上强加密如果这套系统要上线到公网建议换 BCrypt 这类加盐哈希代码改动也不算大就是util里加一个加密工具类再把登录校验处换一下。有了 token 之后后端的拦截器会拦截除登录接口以外的所有请求从请求头里取出Authorization字段校验 token 是否有效、是否过期Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.hasText(token) JwtUtil.validateToken(token)) { return true; } response.setStatus(401); return false; } }这套逻辑本身不复杂但值得学习的是“白名单”的概念——登录接口、图片访问路径这些本来就不需要带 token 的请求要在WebMvcConfigurer里放行否则会把自己卡死。2.3 文章分页与多条件查询的实现思路文章管理是整个系统的核心模块。列表页要支持按标题模糊搜索、按栏目筛选、按上下线状态筛选还要分页这套查询在 MyBatis-Plus 里面写起来非常顺手。LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(title), Article::getTitle, title) .eq(categoryId ! null, Article::getCategoryId, categoryId) .eq(status ! null, Article::getStatus, status) .orderByDesc(Article::getIsTop) .orderByDesc(Article::getCreateTime); PageArticle page new Page(pageNum, pageSize); articleMapper.selectPage(page, wrapper);这里有个容易被忽略的细节like、eq这些方法的第一个参数是布尔表达式只有条件成立时才会追加这个查询条件。比如用户在查询参数里没传titleStringUtils.hasText(title)就是 false这个模糊查询条件就不会被拼进 SQL。这是 MyBatis-Plus 最实用的特性之一能避免你手动写一堆if判断去拼接查询条件。分页插件在config包里配置了一个MybatisPlusInterceptor添加了分页拦截器所以上面selectPage才能正常生效。如果你拿到源码后自己加新的分页接口一定记得确认这个配置存在。置顶文章的排序也很有讲究置顶的排前面按置顶 DESC再按创建时间 DESC。这样既能保证重要内容一直在列表头部又不会让时间字段完全失效。2.4 文件上传与静态资源映射后台管理离不开图片上传文章封面、轮播图、Logo 都要传。这套系统采用的是最直接的本地磁盘存储方案前端把文件通过 POST 请求传给后端后端保存到配置的磁盘路径然后把可访问的 URL 返回给前端。核心代码逻辑大致是这样PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { // 生成唯一文件名防止重名覆盖 String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; // 保存到配置的本地路径 File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, newFileName)); // 返回浏览器可访问的 URL return Result.ok(/uploads/ newFileName); }如果你只做完上面这几步很快会发现问题上传成功了但浏览器访问图片返回 404。原因很简单——SpringBoot 默认不会把本地磁盘上的/uploads/目录暴露成静态资源需要手动配置映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceHandler(file: uploadPath); }这个/uploads/**路径要和上传返回的 URL 对应好。以后做二次开发如果加了新的上传类型比如 PDF、视频也要走同一套映射逻辑。3. 前端 Vue 工程——路由守卫、请求封装与页面实现3.1 为什么是 Vue 2 Element UI以及能少踩哪些坑拿到源码看package.json就能发现这套系统用的是 Vue 2 和 Element UI而不是 Vue 3 和 Element Plus。很多读者会纠结要不要升级我的态度是不需要也不建议一上来就升级。这套源码里大量组件、方法都是基于 Vue 2 响应式原理和 Element UI 的组件 API 写的比如表格的el-table、表单的el-form、弹窗的el-dialog。Vue 3 虽然兼容大部分写法但 Element Plus 的很多属性名、事件名、插槽方式都做了调整直接跑起来会有一堆警告和样式错位。如果你对 Vue 3 没有刚需就用源码自带的版本。Vue 2 的另一个实际优势是相关报错的搜索命中率极高。一个编译错误复制到搜索引擎里基本第一页就能找到解法和原因分析对于刚接触前端工程的人来说被卡住的时间会大幅缩短。3.2 路由模式和登录守卫为什么用 hash 不用 history这套前端路由用的 Vue Router 的 hash 模式也就是地址栏里带#/的那种。我看到不少新手会疑惑history 模式地址栏更干净为什么不选它原因很实在hash 模式下的路由变化不会向服务器发请求刷新页面、直接访问子路由都不会报 404。而 history 模式依赖服务器配置 rewrites如果你把打包后的 dist 放到 Nginx 里但没配try_files用户一刷新后台页面就是白屏。这套源码为了做到“可直接运行”选择 hash 模式是最稳的方案。再看登录守卫代码写在router/index.js里router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { return next() } if (!token) { return next(/login) } next() })逻辑很简单没有 token 一律踢回登录页。有了这个守卫你在没登录的情况下直接访问/#/article/list会先跳到登录页登录成功后再回到目标页面。应用级别的访问控制有了基本保障。3.3 axios 统一封装——拦截器里做的事比你想的多前端 API 请求全部走utils/request.js里封装好的 axios 实例。封装的核心思路是把 token 注入、错误提示、状态码处理全部收敛到拦截器里每个业务接口只需关注自己的数据和 URL。const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带 token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理业务状态码 service.interceptors.response.use( response { const res response.data if (res.code 200) { return res } this.$message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) location.href /#/login } this.$message.error(网络异常请稍后重试) return Promise.reject(error) } )这段代码里最值得关注的是 401 处理token 过期或被篡改时后端返回 401前端拦截器统一清掉本地 token 并跳转登录页。这种“一处鉴权、全局生效”的机制保证了任何接口的未授权响应都能让用户回到登录页重新认证。我在实际二次开发时习惯再给 axios 实例加一个service方法把export request(options) { return service(options) }暴露出去这样在页面里写接口调用会非常紧凑代码可读性也高。3.4 后台管理页面怎么组织几个关键页面实现要点整个后台页面围绕layout展开左侧是菜单栏右侧是顶栏加内容区。所有页面组件都通过router-view渲染在内容区里。这种布局模式是后台管理系统的事实标准如果你要加一个新的功能模块照着现有页面的结构复制一个目录出来就行。文章列表页是典型的el-tableel-pagination组合。表格列包括标题、栏目、状态、是否置顶、浏览量、创建时间、操作按钮。状态列用el-tag展示上线是绿色、草稿是灰色一眼扫过去整个表的结构状态非常清晰。操作列有编辑、删除、上下线切换用的是el-button link风格避免操作按钮堆在一起显得臃肿。分页组件绑定current-page和page-size切页时重新拉接口并保持在当前页不回弹。文章编辑页我多说一句。这个页面的关键点不在表单校验而在于“编辑回显”和“上传封面”。打开编辑页时要根据路由参数里的文章 id 请求详情接口把数据填充到表单里要特别注意日期格式后端返回的createTime是数组或者时间戳需要做格式化转换。封面上传用的是上传组件上传成功拿到返回的 URL 后再把 URL 写入表单隐藏字段提交表单时一起传给后端。如果你是自己写页面最常踩的坑就是把整个File对象传给了后端而不是传 URL导致数据库里存了一堆不可访问的二进制路径。4. MySQL 数据库设计与初始化数据——表结构决定业务边界4.1 六张核心表的结构设计数据库脚本一般在项目根目录的sql/site_cms.sql里。建库建表语句我核对过每一张表的字段都覆盖了业务所需的最小集合没有冗余也没有奇怪的关联。表名用途关键字段sys_user后台管理员username用户名、passwordMD5密码、role角色、status状态category文章栏目parent_id父栏目ID、name栏目名、sort排序article文章内容category_id所属栏目、title标题、cover封面图、content正文、status上下线、is_top置顶、view_count浏览量banner轮播图title标题、image_url图片、link_url跳转链接、sort排序friend_link友情链接name站名、url地址、logoLogo、sort排序site_config站点配置config_key键、config_value值、remark备注这里特别注意site_config这张表它是“一种通用配置方案”的体现——用键值对存储任意站点配置项比如site_name、site_logo、icp_number。你不需要为了一个配置项加一个字段只需要加一行记录即可。这种设计非常适合后台管理系统里“随时可能要加配置”的场景。4.2 表之间的关联关系怎么设计核心关联关系只有两条article.category_id关联category.id一篇文章属于一个栏目一个栏目下有多篇文章。前端展示文章列表时通常会 JOIN 栏目表查栏目名列表页显示的是什么栏目就是这么来的。sys_user和article之间通过author字段关联表示文章的创建人。这里选的是冗余用户名的方案而不是存 user_id 再 JOIN省了一次表关联查询。对内容管理系统来说作者名是稳定不常变的所以这种冗余是可接受的。栏目表自身通过parent_id实现树形结构根栏目的parent_id 0。查询栏目树时后端一次性查出所有栏目数据在内存里递归组装成树结构返回前端。这种实现方案在栏目量不大的时候性能完全足够不用上递归 SQL 这种复杂写法。4.3 初始化数据里藏着登录入口数据库脚本的末尾有一段初始化数据里面写死了第一个管理员账号。默认账号一般是admin密码是admin123或123456具体以脚本里的INSERT语句为准。我建议拿到脚本后先打开看这段因为数据库导入成功以后登录用的就是这组账号。如果你要改默认密码不要在数据库里直接改了一个 MD5 值就完事而是先去util包里找到 MD5 工具类跑一个 main 方法生成新的 MD5 串再替换到数据库里。直接把明文密码写进INSERT是新手常犯的错误数据库一旦泄露所有账号都裸奔。5. 跑通整个项目的完整操作路径——从环境准备到登录后台5.1 环境版本清单明确地说这套系统对环境的要求不高但版本必须匹配。我在干净的机器上实测的版本组合如下软件推荐版本说明JDK1.8 或 11SpringBoot 2.x 在这些版本上最稳定别直接上 JDK 17Maven3.6后端依赖管理工具MySQL5.7 或 8.x8.x 需要额外处理时区配置见踩坑章节Node.js14.x 或 16.x前端编译工具链太新容易报兼容性错误IDEIntelliJ IDEA 2021直接导入后端 Maven 项目即可我特别强调一下 Node 版本。如果你电脑装的是 Node 18 以上npm install的时候很容易出现 node-sass 编译失败、OpenSSL 报错这类问题。这个锅不在这套源码而在旧版本依赖和新版 Node 的兼容性上。最省心的做法是装一个 Node 16.20.x然后用npm install装依赖基本不会出问题。5.2 三步修改配置文件跑通这套系统需要动到的配置文件就三个。第一个是前端的.env.development或者vue.config.js里的开发环境代理配置确认开发服务器端口和代理目标地址即可。第二个是后端的application.yml把数据库名、用户名、密码改成你自己的。第三个是上传路径配置。以application.yml为例关键部分是这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/site_cms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 servlet: multipart: max-file-size: 10MB max-request-size: 20MB custom: upload-path: D:/projects/upload/ jwt-secret: 自定义一段随机字符串如果 MySQL 装在本机地址就是localhost:3306数据库名要和导入的库名一致。上传路径建议配成一个绝对路径Windows 注意盘符存在否则文件会创建失败。5.3 完整启动顺序与验证方法启动顺序有讲究后端和前端谁先谁后都行但数据库必须先就绪。先用 Navicat 或命令行创建数据库CREATE DATABASE site_cms DEFAULT CHARACTER SET utf8mb4;然后导入site_cms.sql。在 IDEA 里以 Maven 方式导入后端项目等依赖下载完成后启动Application类。启动成功后控制台会出现 SpringBoot 启动横幅然后在浏览器访问http://localhost:8080/api/auth/info不带 token 应该得到 401 响应说明鉴权拦截器已生效。进入前端目录执行npm install然后npm run dev。看到编译成功提示后浏览器访问http://localhost:8081跳转到登录页。输入默认管理员账号密码登录成功后会进入后台首页看到仪表盘和侧边栏菜单说明整个前后端链路已打通。如果你想验证更多可以立刻创建一个轮播图上传一张本地图片再回到前台首页刷新看 Banner 是否展示。这一套流程走通说明数据库、文件上传、静态资源映射、接口调用全部正常。6. 我在实际运行中踩过的坑——配置、跨域与打包部署6.1 MySQL 8.x 的时区与 SSL 连接报错这是我遇到的第一个坑而且是最常见的坑。如果你用的是 MySQL 8.x没在 JDBC 连接串里配置时区启动后端时会直接抛异常提示The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。解决办法就是在application.yml的连接串尾部拼接url: jdbc:mysql://localhost:3306/site_cms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiserverTimezoneAsia/Shanghai解决时区识别问题useSSLfalse是关掉 SSL 加密连接请求。MySQL 8 默认会尝试建立 SSL 连接如果服务端 SSL 配置不完善控制台里会刷一堆 SSL 连接失败的错误日志虽然不影响功能但会严重干扰排查其他问题。6.2 前端跨域用代理而不是改后端 CORS前端登录时如果遇到跨域报错千万不要上来就把后端全局 CORS 打开。虽然 SpringBoot 里加一个CrossOrigin或全局配置能立刻“解决”问题但这等于把安全边界全部打开任何来源的网页都可以调你的接口生产环境是隐患。这套源码正确且安全的跨域方案是前端代理。开发环境下在vue.config.js里配置devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样浏览器访问的是8081端口的页面页面里的请求/api/xxx由 Node 开发服务器转发到8080端口浏览器端看到的请求是同源的自然不存在跨域问题。生产环境则交给 Nginx 做反向代理后端不需要开放 CORS。6.3 Vue 打包后扔进 SpringBoot 的坑很多教程会告诉你把前端npm run build之后的dist目录扔到 SpringBoot 的src/main/resources/static下后端启动后就能用一个端口同时服务页面和接口。这样做确实可行但有两个麻烦。第一hash 路由模式下的访问没问题但如果你改了路由模式为 history直接刷新子路由会 404。第二前端打包时接口地址必须改成相对路径/api不能写死http://localhost:8081否则打包后在自己的服务器上访问时请求会指向一个不存在的目标。我实际跑部署时更推荐用 Nginx 分开部署静态文件交给 Nginx接口反向代理到后端 Java 进程。这样前后端可以独立升级也不会被 SpringBoot 的静态资源处理拖累性能。如果你只是本地验证用dist放进static的方式图省事是可以的但要把代理和路径问题先处理好。6.4 上传图片在页面上显示 404最后说一个很多人都会踩的坑上传图片返回了 URL数据库里也有路径但页面上就是显示不出来。原因几乎都出在后端的静态资源映射上。你需要在后端配置类里确保有这个配置Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceHandler(file: uploadPath); }注意file:后面的路径末尾要有/而且路径要和application.yml里配置的上传路径一致。Windows 上路径用D:/xxx/这种正斜杠写法避免转义问题。配置完成后重新启动后端再用上传返回的 URL 在浏览器直接访问一次能显示图片就说明链路通了。我个人的经验是前端代码的问题通常有日志可查后端代码的问题通常有报错可看唯独这种静态资源映射问题控制台里干干净净页面就是一片灰最容易让人束手无策。以后遇到前端图片 404第一反应检查资源映射配置而不是去重写前端代码。最后再说点我的真实体会整套系统跑下来这个源码最大的价值不是“功能高级”而是“骨架干净”——它用一套最标准的 Java 后端 Vue 前端 MySQL 的组合把一个典型的网站信息管理系统需要的模块完整呈现了出来。对想做毕设、课设、或者公司内网小型内容系统的人来说这个起点非常合适。真正在实际开发里你大概率不是从零开始写基础模块而是从这类成熟源码上做二次扩展。比如我就在这基础上给文章模块加过标签功能给用户模块加过部门字段整个过程因为原项目分层清晰改动范围控制得很小。希望这份拆解和踩坑记录能帮你少走几步弯路把它真正变成你自己的项目。
返回列表