聊到前后端分离管理系统,很多人第一反应就是"又一个增删改查"。但当你真的接触一套面向"获奖知名作家信息管理"的系统时,会发现里面的业务边界、字段设计、权限划分,比普通的管理后台要细得多。最近我把一套基于SpringBoot+Vue+MyBatis+MySQL的完整源码做了一次透读和部署演练,它专门用来管理当代中国获奖知名作家的基本信息、获奖经历、代表作品、荣誉档案等,前后端完全分离,源码和部署文档都是完整的。这篇文章不打算只贴几个截图,我想把整套系统从技术选型、数据库设计、后端接口、前端联调到最终部署的整个过程梳理成一份可以照着操作的实战手册。打算做档案管理系统的,或者想在SpringBoot+Vue项目里找一套规范二开底子的朋友,可以先收藏。
1. 项目核心矛盾:作家档案不只是"增删改查"
1.1 作家信息的特殊字段与关联关系
普通用户管理可能只需要用户名、手机号、状态这几个字段,但作家信息管理完全是另一套逻辑。一个人物档案需要包含姓名、笔名、性别、出生日期、籍贯、民族、政治面貌、文学流派、现任职务、联系方式、个人简介、头像照片,这些是基础字段。更关键的是和外部的关联:他获得过哪些奖项,每个奖项的获奖年份和届次是什么;他有哪些代表作品,作品属于小说、散文、诗歌还是评论;作品的出版年份、出版社、ISBN号要不要记录;是否做过文学期刊的编委,担任过哪些文学活动的评委。数据之间是一对多、多对多的关系,单纯平铺一张表根本做不了。
如果只做简单的CRUD,后台录入一个作家,然后前端列表展示几条记录,这个系统没有实际使用价值。谁需要关心作家档案?可能是文学研究机构要做数据库,可能是地方文联做人才管理,也可能是一个文学平台要维护签约作者的信息。不同场景下需要的字段侧重不同,但共性是一定要能处理"奖项—作品—作家"这三类核心实体的联动。所以我在拿到源码后,第一件事不是看代码,而是先看数据库的ER关系,确认这三块有没有真正建立关联。
1.2 是管理后台,还是内容平台?我明确下来的功能边界
这套系统在功能边界上做得比较克制。它没有硬塞一堆花哨模块,而是围绕"档案管理"这个核心任务设计了六个主要功能块:作家信息维护、获奖经历管理、代表作品管理、荣誉分类管理、用户登录与权限控制、系统数据统计。没有做新闻资讯、没有做社区评论、没有做在线展厅,这些都属于后续可以扩展的内容,但第一版必须保持边界清晰。
这种克制的设计思路很聪明。很多类似项目死在什么都想做,最后管理后台像一个展览馆,每个页面都只有半成品。作家信息系统最重要的指标是"数据完整、检索准确、录入方便"。把边界定清楚之后,前后端接口的数量也就控制了,整个项目大约二十个核心接口,不臃肿,部署和维护成本都很低。对于学习SpringBoot和Vue的人来说,这套代码量也是刚刚好,不会因为太多业务逻辑而看不懂。
1.3 角色权限在作家管理场景下的实际意义
权限设计我一开始觉得是多余的,内部系统嘛,登进去就能用。但实际思考后发现,作家档案和获奖信息往往具有权威性,不能随便一个普通用户都能修改。系统里划分了两种角色:管理员和编辑。管理员拥有全部权限,包括删除作家档案、管理用户账户、查看系统日志;编辑只能进行信息的录入和修改,没有删除权限。这个设计非常贴合实际管理场景。
比如一个文学研究机构,负责整理档案的是编辑人员,他们负责日常数据录入,但为了防止误删或者恶意操作,删除权限必须收拢到管理员那里。另外,编辑的修改操作应当被审计,所以源码里每个更新操作都记录了操作人和更新时间的字段,虽然只是几行代码的功夫,但实际在数据维护时能省去很多扯皮。这就是系统设计里常见的"最小权限"原则,在一个看起来很小的项目里能有这种考量,挺难得。
2. 技术选型复盘:SpringBoot+Vue+MyBatis+MySQL这套组合的取舍逻辑
2.1 为什么不用更重的框架
前后端分离是现在Java后端项目的主流形态,但具体选型时有很多路可以走。有人喜欢重一点的Spring Cloud体系,引入注册中心、网关、配置中心;有人喜欢轻量的SpringBoot单体应用,配一个前端工程。这套系统走的是后者——SpringBoot作为后端服务,Vue作为前端单页应用,MyBatis负责数据库操作,MySQL存储数据。
选择SpringBoot单体而不是微服务,原因很简单:业务规模还没大到需要拆分的程度。作家信息管理系统的并发量、数据量,一个单体服务完全扛得住,而且部署简单、调试方便。如果强行引入微服务,光服务间调用的链路追踪就能让人崩溃。学习阶段更重要的是把业务逻辑和框架用法吃透,而不是被分布式基础设施耗掉精力。我经常跟朋友说,技术选型不是越新越重越好,而是看你的业务半径有多大,一个社区小卖部没必要用仓库管理系统那一套。
2.2 MyBatis与MyBatis-Plus的选择差异
这套系统用的是MyBatis,而不是MyBatis-Plus。有些人看到这一点可能会觉得老派,但我觉得正好可以讲讲两者的差异。MyBatis-Plus确实提供了很多开箱即用的CRUD方法,代码量大幅减少,但它有一个隐性代价:封装太狠,很多SQL细节被藏起来了。对于学习来说,手写SQL能帮你扎实理解table、mapper、resultMap的映射关系,也能在复杂查询问题上理解join和子查询的实际用法。
系统里不止有单表操作,比如查询作家列表时要关联返回最新的获奖信息,按作品数量排序,这种场景用简单的BaseMapper内置方法搞不定,还是要写自定义SQL。MyBatis的XML映射文件提供了很好的空间,一行SQL写得清清楚楚,哪里需要调优,直接按数据库的explain结果改SQL即可。如果你是初学者,我更推荐先用MyBatis把SQL功底打牢,再考虑用MyBatis-Plus提速,两条路都不亏。
2.3 前后端分离后的接口设计约定
前后端分离的项目,接口约定是合作基础。这套系统的接口风格是标准的RESTful,但并没有死板地追求所有动词都变成HTTP方法。核心路径分成几组:/api/auth/login处理登录;/api/author** 处理作家信息的增删改查(对应的有POST /api/author/add、GET /api/author/list、PUT /api/author/update、DELETE /api/author/delete*);/api/award** 处理获奖经历;/api/works** 处理代表作品。这种按资源划分的方式,前后端对接口的语义都能快速理解。
同时,所有响应体都统一封装成了{ code, message, data }结构,code为0表示成功,非0表示业务错误码。这个封装看似不起眼,但在前端联调里价值很大。如果每个接口返回结构不一致,前端每个请求都要单独做判断;现在统一结构,axios的响应拦截器里做一次全局判断就够了。这里也提醒大家,自己做项目时最好先定好接口规范再动手写代码,临时改字段名的代价在联调阶段会翻倍。
3. 数据库设计中的关键表与字段约束
3.1 作家主表设计:基础信息与扩展信息拆开的理由
打开数据库脚本,第一张表就是author_info,也就是作家主表。我没有急着看字段,而是先看它是如何做冗余拆分的。作家信息里有一部分是高频查询字段,比如姓名、笔名、状态、排序权重;另一部分是不太会频繁展示的,比如完整生平、联系地址、邮箱、政治面貌。源码的做法是都放在一张主表里,但通过合理的字段类型和默认值来管理。
这个表的核心字段设计有几个值得注意的地方:姓名和笔名分开存储,因为查询时可能按任何一个名称求索引;出生日期用date类型而不是字符串,便于按年龄段筛选;排序权重sort_order用一个int字段,管理后台可以手动调整置顶顺序。另外,status字段只有两个值:1表示上架(可见),0表示下架(隐藏)。文章后面会讲到,前后端都用了这个状态值来控制列表可见性,数据处理起来非常一致。
3.2 获奖经历、作品列表、荣誉分类的表关系
这是整套数据库设计的重头戏。作家、作品、奖项这三者不能全部塞进一张表,所以源码用三张关联表来处理多对多关系。author_works表记录了作品基本信息,外键关联作家ID,同时拥有作品名称、作品类型、出版年份、出版社等字段。author_awards表记录获奖经历,字段包括奖项名称、获奖年份、获奖届次、颁奖机构、获奖等级。
本来以为这样就结束了,但还多了一张award_category字典表,用来维护奖项类型,比如国家级、省级、行业级。这样做有讲究:如果奖项是纯文本录入,后续想按"国家级奖项"筛选根本没辙,因为每个录入员可能打的字都不一样。有了字典表,录入时直接用下拉框选择,数据口径统一,统计报告才能做出来。这个设计很基础,但很多半路出家的人容易忽略。
3.3 索引、唯一约束与常用统计查询
数据库设计如果只看字段不建索引,数据量一上来就是灾难。这套系统的索引设计得非常克制:作家主表的name和pen_name分别建了普通索引,用于支持按名称模糊查询;author_awards表上对author_id和award_year建了联合索引,因为最常见的查询是"查某个作家在某一年获得的奖项";author_works表对author_id建了索引,查代表作品时通过外键直接取。
除此之外,一个容易忽略的点是唯一约束。例如同一作家同一作品的记录不能重复,同一作家同一年获同一个奖项的记录也应该唯一。源码里用了unique key来避免脏数据,我认为这个约束比在Java代码里做判断更可靠,数据库层面的约束是最后一道防线。系统统计功能需要用到几个简单的聚合查询,比如按获奖年份分组统计数量、按作品类型统计数量,这类SQL用group by配合count就能完成,索引设计好之后执行效率都不错。
4. 后端核心模块实现:从权限拦截到动态查询
4.1 登录认证与JWT拦截器的实现思路
后端鉴权采用的是JWT方式,没有引入Spring Security,而是用拦截器实现。原因在于,这个项目角色只有两类,不需要复杂的OAuth2流程,一个注解配合拦截器就能搞定。用户登录成功后,服务端签发一个带过期时间的token,前端每次请求在header里带上token字段,拦截器校验token有效性,并取出用户角色放进Request上下文。
拦截器的实现顺序是:先定义AuthInterceptor,在preHandle方法里从请求头取出token;用JJWT库解析token,如果解析失败直接抛出401异常;再根据用户角色判断当前请求是否涉及删除操作,如果删除接口但角色不是管理员,则返回无权限。这样一个轻量级的鉴权方案,代码量不到一百行,但把权限的拦截面覆盖得刚刚好。有人会问为什么不用Spring Security,我只能说,对一个学习性质的管理系统,拦截器让你更容易看懂每一步发生了什么。
4.2 作家信息的条件分页查询与模糊搜索
作家列表页面的接口是后端最核心的查询逻辑。它接收的参数包括:当前页pageNum、每页大小pageSize、关键字keyword、状态status、奖项类型awardType。Mapper层接收这些参数,动态拼接SQL。这里没有用注解SQL,而是写了XML中的*标签,配合*条件,实现动态条件筛选。
举个实际的查询需求:"查询所有获得过国家级奖项、笔名包含'山'、状态为上架的作家,按获奖时间倒序排列。"这条需求如果拆成SQL,要关联作家主表、获奖表、奖项分类表,并且要做去重。源码里的写法是先查询获奖表中满足条件的作家ID集合,然后用IN查询回主表,再配合分页插件PageHelper完成物理分页。这种关联方式效率不差,而且比多层子查询更直观。如果你打算二开,建议保留这个思路,避免在Mapper里写一堆嵌套子查询导致性能问题。
4.3 文件上传(封面/头像)与静态资源映射
作家头像和作品封面是典型的文件上传场景。后端代码采用的是本地上传方式,配置了上传目录为项目根目录下的upload文件夹。上传接口接收MultipartFile,校验文件大小和扩展名,把文件名改成UUID加后缀的形式,避免重名覆盖。保存成功后返回给前端一个可访问的URL路径。
这里有一个非常容易踩的坑:SpringBoot默认的静态资源路径并不包含你自定义的上传目录,如果你直接访问upload/xxx.jpg会返回404。源码里是通过WebMvcConfigurer的addResourceHandlers方法,把*/upload/**路径映射到文件系统的实际目录。我部署的时候第一次就忘了这个配置,头像一直加载不出来,后来检查控制台才发现资源映射没生效。这个细节在前后端分离部署中尤其需要注意,Nginx也会参与一层路径代理,后面部署部分会再讲到。
4.4 事务与批量导入的注意事项
批量导入作家数据是系统里的一个隐藏功能,界面上有一个"Excel导入"按钮,后端接收Excel文件后用EasyExcel解析,逐行插入数据库。这里最核心的就是事务控制:要么全部成功,要么全部回滚,不能出现导入了一半数据、剩下几行失败的情况。
Service层方法上标注了*@Transactional(rollbackFor = Exception.class)*。这里注意,rollbackFor必须写,因为Spring默认只回滚RuntimeException,如果捕获了异常没抛出去,事务是不会回滚的。源码在导入逻辑里还做了数据校验,比如姓名不能为空、出生日期格式必须合法、笔名如果重复则跳过。我在测试时故意造了几条脏数据,发现回滚效果很稳定。另外,批量插入没有用 for 循环逐条 insert,而是通过SqlSession批量插入,速度提升很明显,几百条数据几乎是秒级导入。
5. 前端页面与接口联调:Vue组件的组织方式
5.1 页面路由与菜单结构设计
前端用的是Vue 2 + Element UI,毕竟是成熟稳定的一套搭配。路由设计采用嵌套路由,主布局组件Layout里包含侧边栏菜单和顶部导航,子页面路由作为children挂在Layout下面。菜单结构是:作家管理、获奖管理、作品管理、荣誉分类、系统统计、用户管理。侧边栏菜单的图标和标题直接通过路由meta信息渲染,避免了在sidebar组件里写死一堆v-if逻辑。
这种组织方式的好处是,以后想加一个新页面,只需在路由表里加一个对象,侧边栏会自动生成对应入口,不需要改动Layout组件。对于二开的人来说,理解这一点能节省不少体力。同时,路由守卫里做了登录校验,如果本地没有token,跳转任何页面都会被重定向到登录页。这是前后端分离项目的基本操作,但源码的写法比较规范,把meta中的roles字段和store里的用户角色做了校验,不是简单的"有token就放行"。
5.2 表格、表单、弹窗的复用思路
作家列表页面是整个前端的样板房,它几乎包含了所有常见操作:表格展示、分页、条件搜索、新增弹窗、编辑弹窗、删除确认、状态切换。源码里把搜索表单、表格列定义、弹窗表单都封装成了独立的data属性,而不是散落在模板里,这样代码的可维护性会好很多。
具体到组件层面,没有过度封装。对话框使用Element UI的el-dialog,表单使用el-form,表格使用el-table,直接写起来并不复杂。我觉得这个度把握得挺好,有些项目喜欢把所有东西都封装成通用组件,参数一大堆,最后维护成本反而比直接写还高。这个系统里每个页面可以独立维护,学习时也容易对照页面和API的关系。
5.3 axios封装、请求拦截与错误处理
前端与后端交互的桥梁是axios,但几乎没有项目会直接在每个页面里写axios.get,而是统一封装一个request工具类。这套系统也不例外。封装的核心有三个功能:baseURL统一指向后端地址,请求拦截器为每个请求自动添加token,响应拦截器对code非0的情况做全局错误提示。
这里有个小设计我特别喜欢:响应拦截器里针对401特殊处理,当token过期或非法时,自动清除本地登录信息,跳转到登录页。这个处理放在全局,后端所有接口都能获得保护。另一个细节是错误提示用了Element UI的Message组件,并且在每次请求的finally里关闭全局loading。如果没有这个封装,每个页面都得自己写loading状态,代码会非常啰嗦。二开时如果想接入新的后端接口,只需要在对应api模块里加一个方法,然后调用request即可。
5.4 前后端联调时最容易踩的接口字段坑
联调阶段是最容易让人抓狂的,但多数坑都是低级问题。比如后端返回的时间字段是"2024-01-05 12:00:00"这种字符串,前端直接拿去传给DatePicker组件是会出问题的。源码里后端统一在实体类字段上用了@JsonFormat注解,保证了返回格式稳定,前端也就不需要做额外转换。
另一个坑是Long类型精度丢失。Java的Long类型如果超过17位,到了JavaScript侧精度会丢失。作家ID如果是雪花算法生成的19位Long,直接放到前端,表格里显示的最后几位会变成0。这个问题在后端序列化时需要解决,源码里用了Jackson的注解把Long转成String输出。很多人忽略这一点,联调时数据从100条变成几百条才暴露出来。我建议做前后端分离的Java项目时,从一开始就把这个处理加上,不要等到上线前再改。
6. 从源码到部署上线:完整操作流程与真实踩坑记录
6.1 环境准备:JDK、Maven、Node、MySQL版本选择
部署这套系统前,环境版本要提前核对好。后端使用的是SpringBoot 2.5.x,对应JDK需要1.8以上,推荐用JDK 8或11,不要贸然用17以上高版本,因为部分旧依赖在更高JDK下可能报模块化问题。Maven用3.6以上,Node环境用14或16版本,如果装了18以上版本,后面跑npm install时某些旧依赖会对Node版本报错。
MySQL推荐5.7或8.0,源码里SQL脚本用的是utf8mb4字符集,8.0对utf8mb4的支持更完整。不要用5.5,容易遇到编码问题。准备就绪后,先启动数据库,执行源码中提供的init.sql,初始化所有表结构和默认数据。默认管理员的账户密码也在脚本里,登录后第一件事建议修改,因为源码里很多演示账号的密码都是公开的,部署在公网时不改会非常危险。
6.2 后端打包与配置文件修改要点
后端配置文件是application.yml,部署前需要修改三个地方:数据源地址、Redis相关配置(没有用到可以忽略)、JWT密钥。数据源配置关键是把数据库URL改成你的服务器IP,密码改成实际值。需要注意,MySQL连接串最好加上useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai,避免中文乱码和时区问题。
打包命令很简单,在项目根目录执行mvn clean package -Dmaven.test.skip=true,成功后会在target目录下生成一个可执行jar包。这里有一个重要提醒:如果生产环境没有外网,依赖下载阶段要在能联网的机器上先执行mvn dependency:go-offline,或者直接用带仓库的打包镜像,否则到服务器上会卡在下依赖这一步。我平时习惯用Maven的settings.xml配置阿里云镜像,速度会快很多。
6.3 前端构建与Nginx代理配置
前端项目在vue-admin目录下,先执行npm install安装依赖,再执行npm run build构建。构建完成后,dist目录就是静态文件。不要直接把dist目录扔给后端jar包,那是单页应用的做法,前后端分离后需要用一个Web服务器来托管静态文件,并做API反向代理。
我用的是Nginx,配置分为两部分:location /匹配前端静态文件;location /api/反向代理到后端服务的8080端口。注意代理转发时要带上proxy_set_header Host $host,否则后端拿到的Host不对,某些业务无法正确识别来源。另一个关键点是history路由模式下的try_files配置,必须写成:try_files $uri $uri/ /index.html,不然刷新页面就会404。这个坑几乎每个用Vue Router history模式部署的人都会踩一次,一定要提防。
6.4 部署过程中三个高发问题
第一个问题是跨域配置。虽然Nginx已经做了代理,但如果你在本地通过8080端口直接访问前端,然后请求后端9090端口,就会出现跨域。源码里后端也开启了CORS配置,但是上线时如果走Nginx同域代理,其实可以不用跨域,后端CORS配置反而会造成一些小麻烦。建议上线后关掉后端的CORS,统一走Nginx转发。
第二个问题是上传目录的权限。后端上传文件后,Nginx访问不了,通常是因为上传目录的所有者不对。Linux下需要把upload目录的属主改成启动后端服务的那个用户,或者用chmod -R 755给足权限。很多部署教程里都不提这个,但实网上跑起来后,头像上传成功却访问不了,问题就出在这。
第三个问题是数据库备份。我之前吃过亏,数据跑了一阵,误操作删掉一条重要记录,才发现没做自动备份。部署完这套系统后,建议立刻写一个MySQL定时备份脚本,每天凌晨备份一次。数据无价,这一点比任何高级功能都重要。
7. 二开建议与个人总结
7.1 如果想加入按奖项筛选、统计图展示怎么扩展
这套系统的数据库已经支持按奖项类型关联查询,但前端目前还没有把"按奖项筛选"做成显性的筛选项。二开时可以在作家列表的搜索栏中加入一个"奖项等级"下拉框,参数传给后端的query方法,SQL里通过left join award_category实现过滤。统计图展示推荐用Vue版本的ECharts,数据接口可以直接复用按年份分组那条SQL,返回JSON后渲染成柱状图或折线图,展示每年获奖人数的变化趋势。
这类扩展难度不大,但要注意保持现有代码风格。比如新加的SQL不要到处复制,而是统一写进AuthorMapper的XML文件里;新加的页面尽量复用现有的表格和弹窗模式。框架本身不限制你加功能,真正影响后面开发效率的是代码是否还能保持清晰。
7.2 源码中需要替换的硬编码项
最后把源码里需要替换的硬编码项列一下,方便大家快速上线:第一件事是JWT密钥,源码里默认是123456这种字符串,必须替换成足够长的随机字符串;第二件事是数据库密码,直接在application.yml里换掉;第三件事是默认管理员密码,登录后响应里会提示修改。还有一个容易忽略的点:Nginx配置里如果有域名,要记得把server_name改成自己的域名,否则用IP访问时会有主机头校验问题。
还有一个小技巧,就是前端构建时把baseURL改成相对路径,不要写死成http://localhost:9090,这样前端静态资源放到任何域名下都能自适应后端代理地址。我用这种方式部署后,把dist目录从测试服务器搬到正式服务器,完全不用重新改配置。这些都是实战中逐步积累的经验,源码里其实很多默认配置只是帮你把项目跑起来,真正要让系统可靠运行,这些细节一项都不能漏。