免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

SpringBoot2+Vue3+MyBatis-Plus知识管理系统源码解析与部署实践

SpringBoot2+Vue3+MyBatis-Plus知识管理系统源码解析与部署实践 我最近在帮一位朋友调一套 Java Web 知识管理系统源码技术栈是 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0还附带一份完整的项目文档。这个组合现在非常主流一套代码下来前端、后端、数据库、文档全齐很适合做二次开发也适合打算入门企业级项目的人去琢磨。前几天我从搭建环境到把前后端都跑通然后又完整走了一遍部署流程中间踩了不少坑也梳理出很多值得记录的细节。今天这篇文章就把这套系统怎么理解、怎么改、怎么部署的完整经验分享出来。这类知识管理系统说到底就是“把散落在个人电脑、聊天记录、大脑里的知识集中存起来并提供检索和分享能力”。它不是在做一个博客而是更像一个轻量级的企业 Wiki。我在看源码的时候第一件事就是先对着文档理清了模块边界不然一打开几百个 Java 文件很容易被细节淹没。等你真正吃透了它会发现这套骨架不仅适合做知识库改一改就能变成个人博客、团队文档中心甚至是一个简单的 CMS 内容管理系统。1. 项目整体设计与技术栈选型1.1 这套系统到底解决了什么问题知识管理系统的核心价值可以用一句话概括让知识“存得进去、找得出来、管得起来”。我在源码里看到的模块也是围绕这三个动作展开的。用户注册登录让你能识别“谁在存”分类、标签、全文检索让你能快速“找”角色权限、附件管理、审核流程让你能控制“谁能看、谁能改”。很多新手拿到源码会急着跑起来但我建议你先在纸上把用户角色划一遍管理员、普通用户、游客分别能干什么。这个系统默认的角色设计是管理员可以管理所有分类和文章普通用户只能维护自己的内容游客只能浏览公开数据。这样的边界清晰后续做权限扩展的时候也方便。从功能清单上看这套源码包含的知识管理功能比较完整首页统计、知识分类树、文章发布与编辑、标签打标、文章评论、附件下载、用户管理、角色管理、菜单管理、操作日志。单看每个功能都不复杂但组合在一起就需要考虑数据关联和接口设计。这就是它比“单纯增删改查 Demo”有价值的地方。你去参加面试或者做毕业设计能讲清楚这些功能背后的表结构设计和权限控制策略就能比大多数人强得多。1.2 为什么用 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0先聊后端。SpringBoot2 在市面上已经稳定运行了很多年无论是资料还是踩坑案例都非常多。相对于 SpringBoot3 默认基于 Jakarta EE 9、要求 JDK17很多企业生产环境还停留在 JDK8SpringBoot2 配合 JDK8 是目前兼容性最好的组合。如果你的目标只是快速交付业务不是追新版本那么 SpringBoot2 不会拖你后腿。而且这套源码用的是 2.7.x 版本属于 SpringBoot2 生命周期后期最稳的版本官方维护时间也很长。再说持久层。MyBatis-Plus 不是新东西但它在中小项目里的体验确实比原生 MyBatis 舒服很多。单表 CRUD 不用写 SQL条件构造器可以像写代码一样拼查询条件分页插件一加就生效逻辑删除和自动填充都是现成的。对比 Spring Data JPA如果你的团队不是所有人都精通 SQL 和性能调优JPA 生成的 SQL 可能会是一个黑盒MyBatis-Plus 至少让你随时能看到 SQL出了问题也好定位。而且 MyBatis-Plus 没有牺牲你写复杂 SQL 的能力遇到多表关联或报表查询你可以在 Mapper 里自定义 SQL这是它最实用的一点。再聊前端。Vue3 的组合式 API 比 Vue2 的 options API 在逻辑复用上清晰得多。配合 Vite开发服务器冷启动基本在 1 秒以内热更新也快。Element Plus 对后端管理系统来说是最省事的组件库表格、弹窗、表单这些东西已经做得非常完善。MySQL8.0 则提供了 utf8mb4 字符集、窗口函数、JSON 类型还有更靠谱的默认排序规则处理知识文章这类长文本数据比 5.7 稳。这套技术栈选型的核心逻辑就是“不追求最新只追求最稳妥的搭配”实际开发中你会发现这种思路能帮你节省80%的排查时间。1.3 源码目录结构怎么看拿到源码先别急着启动先看目录。前端一般是web或frontend目录后端是标准的 Maven 仓库。后端里比较常见的分层是controller、service、mapper、entity、dto、config、common。我在这个项目里看到common下面放着统一返回类Result、全局异常处理器、自定义异常和工具类这就是很多企业项目的标配。你找代码的时候不要满屏乱翻先定位common和config这两个包会告诉你项目的基本规范和配置项。前端目录用 Vite 初始化后src下面通常会有api、views、components、router、store、utils。api目录对应后端每一个模块的请求封装store里存的是用户状态和权限菜单。这套结构很清晰但未必适合所有项目。如果你的业务足够复杂可以考虑在views下面按业务域再分子目录比如views/knowledge、views/system。我拿到这套源码之后会先把后端application.yml里的端口、数据库连接、Redis 配置看一遍再对一遍前端.env.development里的接口地址就能对整个请求链路有画面感。2. 后端核心实现与 SpringBoot2 实践2.1 基础工程搭建与依赖版本注意点后端是基于 Maven 构建的核心依赖我整理成了这样一个最小集合。需要注意版本不要把依赖版本报错当成业务 Bug。依赖建议版本说明spring-boot-starter-parent2.7.18SpringBoot 2.7.x 系列的最后版本spring-boot-starter-web跟随 parent内置 Tomcatmybatis-plus-boot-starter3.5.3.1 及以上兼容 SpringBoot2mysql-connector-j8.0.33必须使用com.mysql.cj.jdbc.Driverlombok跟随 parent减少实体类样板代码spring-boot-starter-validation跟随 parent参数校验spring-boot-starter-security跟随 parent认证授权也可以换成 Sa-Token我比较想提醒的是 MyBatis-Plus 和 SpringBoot2 的版本搭配。之前我用过一个比较老的 MyBatis-Plus 版本在启动时出现ClassNotFoundException: MybatisPlusInterceptor之类的问题。因为老版本的配置类名称和分页插件构造方式完全不同。现在建议直接使用 3.5.3 以上的版本分页插件通过MybatisPlusInterceptor配置逻辑简洁很多。另外MySQL 8.0 的驱动类名已经从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver如果是第一次用 8.0这一步容易忽略。application.yml里的关键配置我会这么写server: port: 8088 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/kms_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这个配置里最容易被坑的是serverTimezone。如果你的数据库连接没有指定时区查询日期字段时经常会出现相差 8 小时的问题。中国时区直接用Asia/Shanghai不用 GMT8因为后者在夏令时切换时可能不准确。另外allowPublicKeyRetrievaltrue是 MySQL8.0 连接时如果遇到Public Key Retrieval is not allowed错误加的参数本地开发通常需要生产环境如果你用的是 SSL 加密连接可以按安全要求调整。2.2 MyBatis-Plus 在项目里的实用技巧这套源码里用了很多 MyBatis-Plus 的特性我总结了几个在真实项目里高频使用的点。第一个是分页插件。很多人配置了分页插件但发现分页不生效多半是拦截器没有正确注册。正确做法是定义一个配置类Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(100L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit是防止有人通过前端传一个超大页码把数据库拖垮这个细节我很推荐加上。分页插件只对IPage类型的参数生效所以你的 Service 层最好返回PageT而不是自己去拼 limit。第二个是逻辑删除。这种管理系统里的知识文章用户删除时不应该直接物理删除而应该标记为已删除避免误删。实体字段加TableLogic然后全局配置里设置好删除值和未删除值即可。需要注意逻辑删除字段必须和数据库表里的字段对应并且在查询时 MyBatis-Plus 会自动追加deleted0条件。如果你想在特殊场景下查已删除数据可以自定义 SQL绕过逻辑删除。第三个是自动填充。创建时间和更新时间不应该让前端传而是让后端自动处理。我会定义一个MetaObjectHandlerComponent public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }实体上对应字段加TableField(fill FieldFill.INSERT)和TableField(fill FieldFill.INSERT_UPDATE)。这里有个细节如果你不是每次更新都手动设置更新人最好也加上自动填充更新人字段方便后面排查操作记录。第四个是条件构造器。源码里大量使用LambdaQueryWrapper而不是写QueryWrapper里面拼字符串这样可以避免字段名硬编码。比如查询某分类下的已发布文章LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.eq(Article::getCategoryId, categoryId) .eq(Article::getStatus, 1) .orderByDesc(Article::getCreateTime);写起来即安全又直观。如果遇到条件比较多建议在 Controller 层直接组装 DTO 查询对象Service 层统一处理空值判断不要把一长串 wrapper 暴露到 Controller。2.3 认证授权与安全设计知识管理系统最大的特点是有“私有知识”和“公开知识”的区别。所以后端的权限控制是这套系统的一个重点。我在源码里看到的是 Spring Security JWT 的方案这也是目前最主流的一种。Spring Security 负责过滤链和权限校验JWT 负责无状态登录。登录流程一般是用户提交用户名密码后端校验成功后返回 token后续所有请求在 Header 里携带Authorization: Bearer xxx。我在实现时通常会写一个JwtAuthenticationFilter继承OncePerRequestFilter在这个过滤器里解析 token并把用户信息放入SecurityContextHolder。权限注解可以用PreAuthorize(hasRole(ADMIN))或PreAuthorize(hasAuthority(system:article:add))。用权限码比用角色更灵活因为角色一般不会太多而权限码可以精确控制到具体按钮。这里有一个比较隐蔽的问题Spring Security 默认的 CSRF 防护在纯前后端分离模式下会干扰 POST 请求所以一般要禁用 CSRF。同时因为我们是 API 服务不需要重定向到登录页所以不需要配置登录表单。当你把源码里默认的用户密码加密方式改成 BCrypt 之后注意数据库中已经存在的密码哈希是旧的 MD5 还是 BCrypt如果是旧数据要写一个迁移逻辑或者在登录接口做兼容否则会出现“所有老用户都登录不了”的尴尬。如果不想用 Spring Security 那一套复杂的过滤器链也可以换成 Sa-Token代码量会少很多。但既然这套源码里已经有 Security我还是建议先跟着源码定制因为面试或实际企业项目里 Spring Security 的出场率更高。2.4 Controller 层规范与统一返回体拿到这套源码最应该模仿的是它统一返回体和异常处理的写法。项目里几乎所有的 Controller 都是返回ResultT里面包含 code、message、data 三个字段。前端 axios 拦截器只要判断 code 是不是 200 就能决定是否弹错误提示。如果每个接口的返回形状都不一样前端处理起来会非常痛苦。我通常会把Result定义成泛型类提供Result.success(data)和Result.error(code, msg)等静态方法。然后在 Controller 里不要再写 try-catch靠全局RestControllerAdvice处理业务异常和系统异常。业务异常比如“分类名称重复”“文章不存在”可以自定义ServiceException校验异常比如参数为空、格式错误由MethodArgumentNotValidException捕获。这样 Controller 会精简到几乎只有业务逻辑。对于新增和修改接口我强烈建议使用 DTO 对象接收前端参数而不是直接把实体类暴露给前端。因为实体类里可能有密码、id 等字段前端不一定该传这些。使用 DTO 以后可以利用NotBlank、NotNull做参数校验再在 Service 层把 DTO 转换成实体保存。这套编码习惯虽然会多几个类但项目的健壮性和可维护性会大幅提升。3. Vue3 前端开发与工程实践3.1 基于 Vite 的 Vue3 项目初始化细节前端工程使用 Vite 构建开发时我常用的命令是npm create vitelatest frontend -- --template vue。创建完成后需要安装路由、状态管理和 UI 库npm install vue-router4 pinia element-plus axios sass这里有几个容易踩的坑。第一Vite 5 之后要求 Node 版本是 18如果你本地还是 Node 16启动会直接报错。第二Element Plus 在 Vue3 项目里需要安装配套的图标库element-plus/icons-vue不然菜单里的图标没法用。第三如果你要用sass定制主题安装完sass后还要在vite.config.ts里配置css.preprocessorOptions.scss的additionalData引入主题变量文件。不然你在某个组件里写的$primary-color根本找不到定义。Vite 开发时跨域处理也很关键。前后端分离开发时前端访问接口通常有跨域问题最简单的办法是在vite.config.ts里配置 server.proxy把/api转发到后端地址。注意这里的“转发”指的是开发服务器帮你转发请求并不是生产环境用的方式。我见过很多新手直接把后端接口地址写在 axios baseURL 里结果浏览器跨域报错还一脸懵。正确姿势是开发环境 baseURL 写/api用 Vite 代理生产环境由 Nginx 做静态资源托管和接口路径转发。3.2 路由设计与动态菜单知识管理系统的菜单往往是根据当前用户权限动态渲染的。如果前端把每个用户的菜单都写死在路由表里一旦权限变动就要重新发布前端代码非常不合理。更好的做法是登录后后端返回当前用户拥有的菜单列表前端动态组装路由。这套源码里的动态路由用了import.meta.glob来批量加载views下的所有组件文件这样路由表可以由后端菜单记录中的 component 字符串动态映射到对应的 Vue 组件。比如后端返回一个菜单记录路径是/knowledge/list组件字段是knowledge/list.vue前端就能通过import.meta.glob(./**/*.vue)找到对应组件。这个方法在 Vite 里很好用比 Vue2 时代用require()引入动态组件更科学。动态菜单处理好后还要注意刷新页面时路由丢失的问题。因为用户刷新后Vue 应用重新启动要先从 localStorage 或缓存里把用户信息恢复再重新生成动态路由。如果这一步没有做就会出现“刷新后页面白屏”或者“访问路由匹配不到组件”的问题。解决思路是在router.beforeEach里判断router.getRoutes()是否已经包含动态路由如果没有则根据用户权限重新添加。3.3 Pinia 状态管理与持久化Vue3 项目里状态管理我首推 Pinia。对比 Vuex 4Pinia 的语法更清爽没有mutations这个额外的概念直接修改 state 就可以了。在这个项目里Pinia 主要存三个东西token、用户信息、菜单权限。我一般会单独创建一个store/user.jsexport const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: {}, routes: [] }), actions: { setToken(token) { this.token token localStorage.setItem(token, token) }, setUserInfo(info) { this.userInfo info }, logout() { this.token this.userInfo {} localStorage.removeItem(token) } } })为什么要用 localStorage因为刷新页面时 Pinia 里的 state 会重置。只把 token 存到 localStorage刷新后还能通过 token 恢复用户信息如果你把用户信息也存到 localStorage要小心过期问题。最好在路由守卫里再发一次getUserInfo请求用后端返回的最新数据覆盖本地缓存。3.4 Axios 二次封装与统一鉴权前端请求必须统一经过 axios 实例。我会在utils/request.js里创建一个实例设置baseURL、超时时间并在请求拦截器里带上 token。响应拦截器里统一处理后端返回的 code。如果 code 是 401说明 token 过期需要跳转到登录页并清空用户状态。这里有个经验不要把后端的 HTTP 状态码和业务状态码混为一谈。有些后端在业务失败时也返回 HTTP 200只是在 body 里的 code 标记为错误有些后端则会把 HTTP 状态码也设置成 400、500。这套源码里使用的是前者。所以你需要在响应拦截器里优先判断 body 的 code而不是一看到后端返回 401 就弹出“登录过期”。具体要看项目文档约定。axios 在文件上传场景下比较特殊。上传知识附件时要设置Content-Type: multipart/form-data并且上传进度条可以用onUploadProgress拿到。但注意不要把响应拦截器的统一处理逻辑套用在文件下载上因为下载接口返回的是 Blob你一旦把 Blob 转成 JSON 去解析文件就坏了。通常我会对下载请求单独创建实例或者在拦截器里根据response.responseType判断是否走默认处理。3.5 核心页面与组件实现思路知识列表页是整个系统最重要的页面。它不但要支持表格展示还要支持搜索、分页、批量删除、标签筛选。用 Element Plus 的el-tableel-pagination很快就能搭出来。但有几个细节需要注意。第一个是表格列的自定义插槽。比如状态列要显示成标签样式操作列要放编辑删除按钮都要用#default{ row }这种插槽写法。Vue3 中作用域插槽的语法和 Vue2 有些区别别搞混。第二个是搜索表单和表格的联动。搜索条件应该放在ref响应式对象里点击搜索时重置到第 1 页然后调用查询接口。每次搜索都要带上页码和每页条数后端才能正确分页。清除筛选时也要恢复初始状态。第三个是富文本编辑器。知识文章通常需要编辑富文本内容我建议使用比较成熟的组件比如 wangEditor 或者 TinyMCE。部署到生产环境时富文本编辑器还需要处理图片上传接口、视频嵌入、HTML 清理。这部分很容易被忽略导致用户粘贴了一段 Word 内容后页面样式乱掉。建议在接入富文本编辑器时一定要对粘贴内容做过滤只保留白名单标签防止 XSS 攻击。4. 数据库设计与 MySQL8.0 使用要点4.1 核心表结构设计知识管理系统的数据库表不会特别多但每张表之间都有明确的关联关系。我根据这套源码的业务逻辑梳理出几张核心表用户表、角色表、权限表、用户角色关联表、角色权限关联表、知识分类表、文章表、文章标签关联表、标签表、评论表、附件表。下面列两个最典型的表结构做参考。用户表CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT BCrypt 密码, nickname VARCHAR(50) COMMENT 昵称, avatar VARCHAR(255) COMMENT 头像地址, email VARCHAR(100) COMMENT 邮箱, status TINYINT DEFAULT 1 COMMENT 1启用 0禁用, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;文章表CREATE TABLE kms_article ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, summary VARCHAR(500), content LONGTEXT, category_id BIGINT NOT NULL COMMENT 分类ID, author_id BIGINT NOT NULL COMMENT 作者ID, status TINYINT DEFAULT 0 COMMENT 0草稿 1已发布 2已下线, view_count INT DEFAULT 0, like_count INT DEFAULT 0, deleted TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_category (category_id), KEY idx_author (author_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT知识文章表;在设计文章表时内容字段建议用LONGTEXT不要用TEXT。TEXT最大存储 64KB对于一个包含多张图片、多段代码的文章来说很容易撑爆。分类表和文章表通过category_id关联你可以用递归查询来构建分类树。MySQL8.0 支持WITH RECURSIVE如果你不想像旧版本那样用程序循环组装树用递归 CTE 是很高效的。但很多项目仍然选择在 Java 内存里组装树因为分类数据量通常不大一次查出来再递归构建更简单。标签和文章是多对多关系中间表一般叫kms_article_tag。标签的维护不需要很复杂在保存文章时直接处理如果标签已存在就复用不存在就插入。建议在标签表里加一个文章数量统计字段避免每次查询标签列表时都去 count 中间表性能会好很多。4.2 MySQL8.0 新特性在项目中的实践MySQL8.0 默认字符集已经是utf8mb4排序规则是utf8mb4_0900_ai_ci这里比 5.7 更省心。之前用 MySQL5.7 时如果建表时没有指定字符集很可能中文字符出现乱码而 8.0 的默认配置基本不会有这个问题。在连接串上还是建议显式加characterEncodingutf8mb4因为某些环境下客户端默认字符集不是 utf8mb4。窗口函数是 MySQL8.0 的一大亮点。比如你要查每个知识分类下文章数量排名前3的分类以前需要写复杂的子查询现在可以直接用ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY view_count DESC)。不过在这个管理系统里大部门列表查询还是 MyBatis-Plus 分页解决的窗口函数更多用在做统计报表。如果你需要做“文章热度排行”“用户活跃度排行”这个特性就非常有价值。JSON 类型在知识管理里也有应用场景。比如文章扩展字段像封面图、来源地址、自定义 SEO 信息都可以用 JSON 存储。MySQL8.0 提供了JSON_EXTRACT、JSON_CONTAINS等函数可以在 SQL 层面直接查 JSON 内容。但我不建议把所有扩展字段都扔进 JSON毕竟 JSON 列不能建普通索引如果你要按某个 JSON 属性做查询应该同时做生成列并建索引。4.3 Docker 快速部署 MySQL8.0自己电脑上装 MySQL8.0 有时挺麻烦。如果你只是想快速把项目跑起来用 Docker 是最省事的。我在本机用这样的命令启动docker run -d \ --name kms-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEkms_db \ -v /data/mysql:/var/lib/mysql \ --restartalways \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_0900_ai_ci \ --default-authentication-plugincaching_sha2_password这个命令里有几个参数值得解释。MYSQL_DATABASE会自动创建数据库后面导入 SQL 就不用再单独建库。/data/mysql是宿主机挂载的数据卷防止容器删除后数据丢失。--character-set-server和--collation-server指定了默认字符集保证中文正常。caching_sha2_password是 MySQL8.0 默认的认证插件。如果你在连接容器里的 MySQL 时遇到Authentication plugin caching_sha2_password cannot be loaded说明你的后端驱动太老。解决方法是升级mysql-connector-java到 8.0.x或者你可以在容器里把用户认证方式改成mysql_native_password。不过从长远看升级驱动才是正路。另外在 Docker 里跑 MySQL建议加上--restartalways这样服务器重启后数据库能自动拉起来。生产环境再把端口映射和密码策略收紧一些。5. 部署上线与常见问题排查5.1 前端构建与资源部署前端上线之前要执行npm run build产物会生成到dist目录。直接把dist里的静态文件放到 Nginx 的html目录下或者用docker build构建前端镜像。这里比较关键的是前端路由模式。如果你用的是 history 模式默认就是去掉 # 号那么在 Nginx 配置里必须处理“未知路径回退到 index.html”的情况否则用户刷新一个子页面就会出现 404。Nginx 配置可以这样写server { listen 80; server_name kms.example.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8088/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意proxy_pass这里是 Nginx 的标准用法把/api开头的请求转发给 Java 服务。我这里提到的是“转发”行为不是其他特殊用途。很多同学直接把dist文件和 Nginx 配置做完后发现登录接口 404 或者 502就是因为没有配置location /api/或者后端地址不对。生产环境还有一个常见坑大文件上传。默认 Nginx 的client_max_body_size是 1M知识管理系统经常会传附件或图片超过 1M 就会被 Nginx 拦截返回 413。在http块或server块里加一行client_max_body_size 50m;具体大小根据你的业务调整。如果你还把富文本编辑器上传的图片也走网关那这个限制必须设得足够大。5.2 后端打包与服务启动后端打包用 Maven 执行mvn clean package -DskipTests生成 jar 文件。启动命令很简单java -jar kms-admin-1.0.jar --spring.profiles.activeprod。如果你想让它在服务器上以守护进程方式运行可以配合 Systemd也可以用 Docker。如果用 Docker我会先做一个专用 Dockerfile基于openjdk:8-jdk-alpine并把 jar 包拷贝进去指定健康检查。启动之前还要注意一点application-prod.yml里的数据库地址、Redis 地址等要改成生产环境的值。源码里如果带了application-dev.yml、application-prod.yml配置记得对比差异。我见过不少案例本地改成生产配置后连不上数据库一排查发现是密码里带了特殊字符没有加引号或转义结果整个配置被解析错误。启动后如果发现端口被占用用lsof -i:8088或者netstat -ano查一下。如果日志刷出Error creating bean with name xxMapper先别慌多半是 SQL 映射文件路径没配对或者实体类上的表名注解没写。更多时候是因为没有连上 MySQL这个看日志第一行异常就能看出来。建议启动时开启mybatis-plus.configuration.log-impl: StdOutImpl控制台会输出 SQL 语句排查查询问题非常有帮助。5.3 高频问题速查表我整理了一份在调通这套系统过程中遇到的高频问题按“症状—原因—解决方案”列在下面。症状原因解决方案启动报ClassNotFoundException: MybatisPlusInterceptorMyBatis-Plus 版本过旧或类名不对升级到 3.5.3 以上连接数据库报Public Key Retrieval is not allowedMySQL8.0 认证机制导致连接串加allowPublicKeyRetrievaltrue数据时间比本地早 8 小时serverTimezone没配置连接串加serverTimezoneAsia/Shanghai前端请求接口跨域未配置代理或 CORS开发环境用 Vite proxy生产用 Nginx 配置Vue 刷新页面后路由 404history 模式未回退Nginx 添加try_files $uri $uri/ /index.htmlMyBatis-Plus 分页不生效没注册分页拦截器添加PaginationInnerInterceptorBean上传文件报 413Nginx 默认限制 1M设置client_max_body_size 50m;登录接口返回 401token 未传或过期检查请求头是否带Authorization其中“跨域”问题值得多聊两句。很多新手在开发环境遇到跨域会去后端加CrossOrigin注解或者写一个 CORS 过滤器。这当然能解决但是到了生产环境你又不可能让前端去访问后端的 8088 端口所以最合理的方式是生产环境把前后端放在同一个域名下用 Nginx 的路由规则把/api转发到后端。这样就不会有跨域问题。后端 CORS 配置可以保留但不要依赖它来根治问题。5.4 从日志定位问题的思路排查这套系统问题我会按照“前端请求 → 后端接口 → SQL 执行 → 数据库结果”这条链路来。前端打开 F12 看 Network 面板确认请求 URL、请求方式、请求参数和响应状态码。如果请求 404先看后端控制器路径是否对得上如果是 500直接看后端控制台日志堆栈定位到具体哪一行抛异常。如果是 SQL 执行报错看日志中打印出来的 SQL 语句把它复制到 Navicat 里执行一遍大部分问题都能当场发现。很多时候报错是“懒”出来的。比如用户列表查询时时间参数传空导致 SQL 里出现create_time null数据库里就直接报类型不匹配。这种情况我会在 Service 层对查询条件做一遍空值校验不要只想着让 MyBatis-Plus 自动处理。开发阶段多打印日志线上阶段再关掉StdOutImpl否则大量 SQL 日志会拖慢性能。日志级别可以设置成info关键链路用debug单独开一个包。6. 含文档版本的使用体验与扩展建议6.1 文档对二次开发的价值这套源码标题里专门提到“含文档”我在实际使用过程中确实感受到文档带来的便利。文档里通常包含数据库初始化脚本、部署步骤、接口说明、项目结构说明。拿到代码后我建议按这个顺序来先看“环境要求”确认 JDK 版本、Maven 版本、Node 版本再按“初始化数据库”把脚本导入 MySQL然后启动后端看接口是否正常最后启动前端登录系统。如果每一步都顺畅说明代码和文档一致性很高。如果某一步报错优先检查是不是环境版本和文档要求不一致。文档里的接口说明对前后端联调特别有价值。没有接口文档你要靠猜或者看前端代码才知道每个接口需要什么参数效率极低。我自己的习惯是每调通一个接口就在文档对应接口后面补一笔当时的请求参数和返回示例这样下次改起来会快很多。如果你准备把这份源码作为毕设或者项目作品文档里的数据库 ER 图、流程图也值得保留因为它们是你答辩时最好的展示素材。6.2 从知识管理系统到其他业务系统这套系统最大的优势在于基础模块非常通用。用户管理、角色管理、菜单管理、日志管理这些几乎每个后台管理系统都需要。如果你想把它改造成其他业务系统思路不是“重新写一套”而是在现有骨架上替换或增加业务模块。比如把“知识文章”替换成“商品信息”再加上购物车、订单表就能变成一个简版商城后台把“分类”改成“栏目”加上“轮播图”“友情链接”就是一个 CMS 内容管理。改造的时候要动的最核心的地方其实是路由和菜单。前端菜单是从后端动态加载的所以只要在数据库菜单表里增加一条新菜单记录前端就能出现新入口。而后端你要做的就是在 Controller 和 Service 里新写一个业务模块。这种“增删改查 权限控制”的模式非常适合用来理解企业级系统是怎么从 0 到 1 搭建的。当然扩展时不要把业务逻辑堆在 Controller 里。我习惯把新增的业务模块单独建一个包比如module/shop下面再分controller、service、mapper、entity。这样和原来的system模块隔离后续升级或者退回都比较方便。6.3 源码学习路线建议如果第一次接触这样的项目不要一上来就盯着代码一行行读。我推荐一条路线先跑通项目然后看数据库表再追一遍“登录接口的完整流程”接着看“文章发布流程”最后看“权限控制实现”。第一步跑通能让你建立信心看表能帮你理解业务模型追登录流程可以让你把前端登录页、axios、后端 Controller、Service、MyBatis-Plus、数据库、JWT 这些点串起来看文章发布可以让你的视野覆盖到文件上传、富文本处理、标签关联这些常见业务场景最后看权限控制你就能理解动态菜单和安全过滤是怎么协作的。这个过程不需要贪多一天看一个环节就够了。遇到不懂的方法用 IDE 的点进去看一眼或者打开源码中对应的 XML 映射文件看它执行了什么 SQL。你能把这个项目完整讲清楚同时能把“为什么这么设计”答上来就已经超过了大部分只会在网上找 Demo 的人。最后我再分享一点个人体会。我在调通这套系统的过程中最大的感受是不要迷信“所有功能都自己从零写”但也不能只停留在“能用”。认真吃透一套成熟源码胜过自己闷头写三个简单项目。特别是 SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0 这套搭配在未来的三五年里依然是中小企业项目的主力。你把它内部的结构、配置方案、权限模型、部署链路都弄明白以后换任何技术栈底层思路都是通用的。遇到问题多去看日志、多去拆解报错条件这是所有“经验丰富”的开发者最真实的工作方式。
返回列表