☰
SpringBoot+Vue3前后端分离:作家信息管理系统从零搭建指南
2026/10/22 15:16:27 网站建设 项目流程

从零搭一套前后端分离的作家信息管理系统,我踩过的坑都在这里

做内容管理系统的人都知道,图书出版、文学研究机构、作协资料库这类场景,表面上要的是一个"信息增删改查",实际最头疼的是检索维度多、数据字段杂、录入标准不统一。最近我用 Java SpringBoot + Vue3 + MyBatis 从零搭了一套"当代中国获奖知名作家信息管理系统",前后端分离,数据库用的 MySQL,把所有常见的作家数据建了模型、做了管理界面,还给了一整套可复用的检索和导入导出方案。

这篇文章就把这套系统的完整构建过程拆开讲一遍,从表结构设计、后端接口实现、前端页面编排,到部署时容易踩的坑,全部覆盖。适合三类人看:一是刚学完 SpringBoot 和 Vue3、想找一个完整项目练手的同学;二是单位里确实需要作家资料库、获奖信息库这类管理系统、想找个参考方案的人;三是已经在做类似信息管理系统,想看看别人怎么设计检索、认证、导入导出这些通用模块的开发者。看完你不仅能照着重做一套,还能避开我当时调试到凌晨的若干经典问题。

先交代一下项目背景。某高校文学资料室要做一套内部使用的知名作家数据库,需求提得其实不复杂:录入作家基本信息(姓名、笔名、籍贯、出生年份、文学流派等),录入代表作品与获奖记录(奖项名称、获奖年份、颁奖机构等),能按姓名、流派、获奖年份这些条件组合检索,能对获奖数据进行统计,还要支持 Excel 批量导入、导出。听起来不难,但真正动工之后会发现,"简单管理系统"背后的细节多到能写一本书。

1. 项目定位与技术选型:为什么是 SpringBoot+Vue3+MyBatis 而不是别的

1.1 这类系统选型时的真实考量

先说结论:这个组合是当下做中小型管理信息系统最"稳"的一套选型,没有之一。SpringBoot 负责后端接口和业务逻辑,Vue3 + Element Plus 做管理端界面,MyBatis 管数据库操作,MySQL 存数据,前后端通过 RESTful API 通信。这套组合的生态太成熟了,遇到任何问题几乎都能搜到现成解决方案。

我见过不少团队在这种项目上纠结过要不要上更重的框架,比如用 RuoYi 这种快速开发平台,或者直接上微服务。我的建议是:需求规模决定架构复杂度。一套服务于内部资料室、日活几十人的管理系统,如果上微服务、上消息队列,纯粹是给自己找罪受。SpringBoot + MyBatis 的轻量特性刚好卡在这个规模的最优点——代码写起来直接,部署就是一个 jar 包,运维成本几乎为零。

Vue3 选得也很有讲究。我之前用 Vue2 写过多套管理系统,这次之所以选 Vue3,不是因为"新"就盲目跟风,而是组合式 API(Composition API)在管理系统的场景下确实好用——一个作家编辑页面的逻辑,用 setup 语法可以把表单校验、数据请求、状态刷新归拢在一个作用域里,比 Options API 分散在 data/methods/watch 里清晰得多。再加上 Vite 的启动速度,开发体验上完全不是同一个时代的东西。

1.2 整体架构与目录规划

前后端分离的意思很容易被误解——不是说写两个文件夹就叫分离,而是前端只通过接口消费数据,后端不关心页面长什么样。这样的好处是:前端和后端可以并行开发,接口定义清楚之后,我写后端的时候同事可以同时写前端页面,最后联调只要对齐接口格式就行。

实际的工程结构我分成三块来规划:

后端:SpringBoot 标准的 controller / service / mapper 三层,外加 config、common、entity、dto 这些包。entity 对应数据库表结构,dto 负责接口出入参,避免直接把数据库实体暴露给前端。

前端:Vite + Vue3 + Element Plus + Pinia 的经典组合,页面放在 views 下,按"作家管理""作品管理""获奖管理""数据统计""系统管理"模块分包,通用组件放 components,接口请求统一封装在 api 目录。

数据库:5 张核心表,分别是作家基本信息表、作品表、获奖记录表、用户表、操作日志表,外加一张流派字典表做数据标准化。

提示:千万别把 dto 和 entity 混在一起用。我见过很多项目为了省事直接把 entity 返回给前端,这样一旦数据库字段调整,接口响应就跟着变,前端所有调用方都要跟着改。分开之后,接口的稳定性会好很多。

2. 数据库设计:一张作家主表如何撑起全站检索,底层逻辑与索引选择

2.1 核心表结构与字段拆解

作家信息管理系统的灵魂在数据库设计。这里的核心矛盾是:作家属性多,但又不能无限制地建字段。姓名、性别、出生年份这种常规字段好办,难的是"文学流派""主要成就"这种一对多、多对多的关系。

我的做法是主题表 + 关联表 + 字典表三层结构:

  • 作家主表(author):id、name(姓名)、pen_name(笔名)、gender(性别)、birth_year(出生年份)、native_place(籍贯)、education(学历)、biography(个人简介)、avatar(头像)、status(状态)、create_time、update_time。
  • 流派表(genre):id、genre_name。作家和流派是多对多关系,通过 author_genre 关联表连接。
  • 获奖表(award):id、author_id、award_name、award_year、award_org(颁奖机构)、work_id(关联的获奖作品)、description。
  • 作品表(work):id、author_id、title、publish_year、publisher、isbn、summary。
  • 用户表(sys_user)和操作日志表(sys_log):负责认证和管理审计。

这里有个容易犯的错:把"流派"做成作家表里的一个字符串字段,值写"先锋派""现实主义"……一旦要按流派筛选,你会发现 SQL 写得无比别扭,而且数据录入只要手抖一下,"现实主义"和"现实主義"就成了两个流派。用字典表 + 关联表的方式,前端下拉选项直接查流派表,录入时只能选不能填,数据脏的问题从根上解决。

2.2 查询性能的底层逻辑与索引优化

作家列表页最常见的查询是:按姓名模糊搜、按流派筛选、按获奖年份筛选、按出生年代区间筛选。这些条件组合起来,如果不在索引上下功夫,作家数据量过十万之后查询一定会慢。

我在 author 表上建了三个索引:

  • 普通索引idx_name:配合LIKE 'xxx%'前缀匹配。
  • 联合索引idx_year_native(birth_year, native_place):按年代 + 籍贯组合筛选时的利器。
  • 普通索引idx_status:状态字段在列表页基本都会用到。

为什么idx_name用前缀匹配而不是LIKE '%xxx%'?这是很多人的知识盲区。普通索引的 B+ 树只能加速"从某个值开始"的查找,一旦%放在开头,MySQL 的优化器会发现无法使用索引树进行定位,只能退化成全表扫描。所以模糊搜索的规则很明确:条件允许就尽量前缀匹配,如果必须模糊中段匹配,应该考虑全文索引或搜索引擎,而不是硬扛 LIKE。

获奖表的索引更关键。award_year和author_id是统计类查询的热点字段,我建了联合索引idx_author_year(author_id, award_year),统计某个作家历年获奖情况时,这个索引可以直接覆盖查询,连回表都省了。MySQL 里 InnoDB 的二级索引叶子节点存的是主键值,所以如果查询列恰好都在索引里,就能走覆盖索引,性能翻倍。这套逻辑同样适用于我们后面要做的"获奖年份分布图"接口——数据从千万级表里按年份聚合时,有索引和没有索引是两个体感。

2.3 数据字典与字段标准化的隐藏价值

前面说了流派用字典表,其实整个系统里凡是"可枚举的选项值",我都建议做成字典。比如"性别"可以不用字典,因为就两个值;但"学历"就值得做——不同人录"本科""大学毕业""学士",其实是一个意思,不统一后面没法统计。

我建了一张通用字典表(sys_dict),结构是 dict_type、dict_label、dict_value 三项。作家学历、获奖级别(国家级/省级/市级)、作品类型(长篇小说/诗集/散文集/评论集)都挂在同一个字典体系下。这样做最直观的好处是:前端做下拉框时,只要请求一个接口/dict/type/{dictType},返回值就能直接渲染成选项;后端做校验时也顺手——不是字典里的值直接打回。录入规范了,后面做统计报表才靠得住。

3. 后端核心实现:从 JWT 登录态到作家模糊检索的条件拼装

3.1 认证与授权:JWT 写进拦截器的完整思路

信息管理系统得有登录,否则谁都能打开接口拿数据,那就成裸奔了。我的方案是 JWT(JSON Web Token) + Spring Boot 拦截器。

登录流程是这样的:用户提交用户名密码,后端校验通过后生成一个 JWT 字符串返回给前端。JWT 的有效期设定为 8 小时,里面封装了 userId 和 userName,使用 HMAC-SHA256 算法做签名。前端拿到 token 之后存在 localStorage 里,每次请求在请求头里带Authorization: Bearer xxx。后端写了一个拦截器,对所有除登录接口之外的请求做校验,token 非法或过期直接返回 401。

这里有个细节值得说:JWT 的过期校验放在哪一层。最稳妥的是放在拦截器里统一做,不要散落到每个 Controller。我写了一个JwtInterceptor implements HandlerInterceptor,在 preHandle 方法里从请求头取 token、解析、校验签名和过期时间,通过后把 userId 放进 ThreadLocal 里,这样 Controller 层直接取"当前登录用户"即可,不用每个接口重复解析 token。如果 token 过期,就返回一个统一结构的错误响应,前端收到 401 后跳转到登录页。

3.2 作家管理的核心接口:条件检索的分页与排序细节

作家列表接口是这个系统里最核心的接口,没有之一。前端列表页的筛选项我设计成这么一组:姓名关键字、流派 ID、出生年份起止、籍贯、获奖状态(是否获过奖)。

在后端,我定义了一个AuthorQueryDTO,把这些参数全部收进去。Service 层做了三件事:

第一,根据流派 ID 查出该流派关联的作家 ID 集合,然后用IN条件过滤主查询;

第二,把出生年份起止转成"某年之前的作家"和"某年之后的作家"——如果只填了起始年没填终止年,就查出所有出生年份大于等于起始年的作家,两个边界条件都是可选构造而不是固定拼接,这在 MyBatis 里要用动态 SQL 实现;

第三,判断"获奖状态"参数——如果用户勾选了"仅看获奖作家",就拼一个EXISTS (SELECT 1 FROM award a WHERE a.author_id = author.id)的子查询。用EXISTS而不是JOIN加DISTINCT的原因是:EXISTS会在命中第一条记录后立刻停止扫描,对于作家表大、获奖记录稀疏的场景,性能明显更好。

MyBatis 里的动态 SQL 我选择了<where>+<if>标签组合,它有一个很贴心的特性:会自动去掉第一个<if>条件前面的AND关键字。这个特性避免了一大堆"where 1=1"的丑陋写法。你可以对比一下这两个写法,前者是新手常规操作,后者才是正规军:

<!-- 新手写法:丑且有一定SQL注入风险 --> <where> WHERE 1=1 <if test="name != null and name != ''"> AND name LIKE CONCAT(#{name}, '%') </if> </where> <!-- 正规范写法 --> <where> <if test="name != null and name != ''"> AND name LIKE CONCAT(#{name}, '%') </if> <if test="genreId != null"> AND id IN (SELECT author_id FROM author_genre WHERE genre_id = #{genreId}) </if> </where>

分页用的是 PageHelper 插件,一个PageHelper.startPage(pageNum, pageSize)后面紧跟查询方法,MyBatis 插件机制会自动把 LIMIT 拼到 SQL 尾巴上,并生成一条 COUNT 查询。要注意的是PageHelper.startPage只对紧接着的下一条查询生效,所以 Service 层里千万别在 startPage 和查询之间插入其他 SQL 操作,否则分页会被"吃掉"——这个问题我排了很久才发现。

3.3 获奖数据的 Excel 导入导出设计与异常处理

这批需求里最让我重视的是 Excel 导入导出,因为"录入标准不统一"的痛点最容易在批量导入时爆发。我用的操作 Excel 的库是 EasyExcel,它相比 POI 最大的优势是写入内存占用低——POI 写入 10 万行数据内存可能飙到几百 MB,EasyExcel 在相同场景下内存占用可以控制在几十 MB 以内,因为它做的是流式写入。

导入设计上,我做了两个前置处理:第一,上传的 Excel 必须有固定的表头模板,后端读取时先校验表头,表头对不上直接拒绝,避免"传错模板"导致的数据错乱;第二,逐行校验数据合法性,比如出生年份必须是 4 位数字,姓名字段不能为空,校验失败的行要收集错误信息,而不是中间崩掉中断整个导入。

具体实现是让监听器继承AnalysisEventListener,在invoke方法里逐行把数据转成 DTO,先做自定义校验再批量入库。收集的List<String>错误信息会写到一张临时错误表中,前端在导入结果弹窗里能看到"第 X 行:姓名不能为空"这类提示,用户可以下载错误行 Excel 修正后重新导入。这个体验远好于"导入失败,请重试"这种黑盒报错。

导出倒简单不少,EasyExcel 提供了write方法,配合一个ExcelWriter做分批写,注解里写清楚列名和顺序。我留了一个小彩蛋:在导出接口上加了异步支持,数据量大时先返回一个"导出任务已创建"的提示,用户去操作日志里下结果文件。异步导出避免了大查询把请求线程卡死的问题,这个思路在管理系统中很实用。

3.4 获奖统计接口:按年份聚合与趋势曲线的实现

统计模块的需求是:在作家详情页展示 TA 的获奖时间线,在全局页面展示"近十年获奖作品数量趋势"。这两个都涉及按时间聚合的查询。

按年份聚合的 SQL 模板基本长这样:

SELECT award_year AS year, COUNT(*) AS cnt FROM award WHERE author_id = #{authorId} GROUP BY award_year ORDER BY award_year

这里涉及一个常见困惑:COUNT(*)和COUNT(1)和COUNT(award_id)哪个快?其实在现代 MySQL 里几乎没有区别,优化器都会选择最小的索引树做扫描。真正需要关心的反而是能否走覆盖索引——我在前面建的idx_author_year(author_id, award_year)联合索引,对这条 SQL 来说就是完美覆盖,查询时不需要回表读整行数据。你如果经常做统计类开发,一定要养成看执行计划的习惯,用EXPLAIN SELECT ...看一下 type 是不是 index 或 ref,有没有 Using index,这是性能调优的基本功。

前端拿到年份和数量的两个数组之后,直接用 ECharts 渲染折线图。这块的交互我后面再细说。

3.5 后端分层规范与统一返回体设计

接口返回格式的统一也是要提前定的规矩。我的统一返回体是Result<T>,结构就三个字段:code(200 成功 / 400 参数错误 / 401 未认证 / 500 服务器错误)、message(提示信息)、data(业务数据)。后端异常用@RestControllerAdvice统一处理,业务异常抛出自定义的BizException,未知异常兜底返回"系统开小差了"。

为什么统一返回体重要?因为前后端分离之后,前端不可能每个请求都单独判断返回格式。统一之后,前端 axios 的拦截器只需要判断一次 code:200 直接返回 data,401 跳登录页,其余弹错误 message。前端代码量直接砍半,这是典型的花五分钟省五小时的设计。

4. 前端核心实现:Vue3 组合式 API 下的作家管理页面与接口编排

4.1 项目初始化的几个关键配置

Vite 创建的项目默认约定src目录,但我会额外做两件事:配置@别名指向src目录,这样所有组件里import xxx from '@/api/author'就不用写一长串相对路径;配置开发环境代理,把/api前缀的请求代理到后端服务的 8080 端口,这样开发时不会遇到跨域问题——跨域是前后端分离开发中最常见的拦路虎,生产环境如果前端静态文件和后端接口不在同一个 Origin,就会报 CORS 错误。开发环境用 Vite 代理解决,生产环境用 Nginx 反向代理解决,这个后面讲部署时会展开。

4.2 作家列表页:查询条件、表格渲染与分页联动

作家列表页的布局是:页面上方一块el-form放筛选条件——姓名输入框、流派下拉框、出生年份范围选择器、获奖状态选择器,中间一排放"查询""重置"按钮,下面一张el-table展示结果,底部是el-pagination分页组件。

这里我重点说查询条件与分页的联动逻辑,这是新手很容易绕晕的地方:

  • 查询按钮:把表单值收集成一个 query 对象,重置 pageNum 为 1,再重新请求。
  • 分页组件 change 事件:只更新 pageNum 和 pageSize,触发请求,但保留现有 query 对象。
  • 重置按钮:清空表单,同时重置分页,重新请求默认列表。

这个联动逻辑写清楚之后,用户在任意筛选状态下翻页都不会丢条件。代码上我用了ref存储 query 和分页状态,通过reactive维护表格数据,请求统一封装在useAuthorList这样一个组合式函数里,列表页组件本身只负责渲染和事件绑定,逻辑内聚度很高。

4.3 表单校验与动态选项加载

作家新增/编辑弹窗是使用频率最高的模块。表单字段有十余个,其中姓名和出生年份必填,流派支持多选,个人简介是 textarea。

Element Plus 的表单校验我用的是el-form的rules属性,姓名必填加上"长度 2-20 个字符"的校验规则,出生年份用自定义校验器验证范围在 1900 到当前年份之间。流派的选项不用写死在页面上——打开弹窗的同时请求/dict/type/genre接口,动态填充el-select的选项。这样如果资料室后来加了新的流派分类,后端字典表加一行数据,前端不用发版就能看到新选项,这个设计对信息系统来说特别值。

4.4 获奖时间线与统计图表的可视化

作家详情页我用了el-descriptions展示基础信息,下方两个可视化模块:获奖时间线和获奖年份统计。获奖时间线用el-timeline组件,按年份倒序排列,每条记录展示奖项名称、获奖年份、颁奖机构;获奖年份统计用 ECharts 的柱状图,展示该作家历年获奖数量。

ECharts 和 Vue3 集成时,我建议不要在组件里直接写echarts.init然后手动管理销毁,而是封装一个BaseChart组件:props 接收 option 对象,watch 到 option 变化就 setOption,组件卸载时自动 dispose。这套封装逻辑在其他任何图表场景都能复用,你后面做数据大屏时直接拿过去改改就行。

4.5 权限控制:按钮级权限与路由守卫如何配合

管理系统的权限设计我实现了两级。第一级是路由守卫:router.beforeEach里判断 localStorage 有没有 token,没有就强制跳登录页;有 token 但访问的是管理员专属页面且当前用户角色不是管理员,就拦下来。第二级是按钮级权限:用自定义指令v-permission判断当前用户的角色是否包含所需权限码,不包含就直接移除按钮的 DOM。

按钮级权限适合"删除""导出""导入"这类敏感操作,普通员工能看数据但不允许删数据。这里的关键是把权限码定义得足够细,比如author:delete、author:export、system:user:add,后端登录时把该用户的权限码列表放进 JWT 的 claims 里,前端解析后存到 Pinia,渲染时统一判断。这套机制完全可以平移到你未来的其他管理系统里。

5. 部署落地:Nginx 反向代理、数据库初始化与常见环境问题

5.1 前端构建与 Nginx 配置要点

前端开发完成后执行npm run build,Vite 会在dist目录下产出纯静态文件。部署时我把 dist 里的文件放到 Nginx 的 html 目录下,然后配置反向代理,把所有/api前缀的请求转发到后端服务的 8080 端口。

Nginx 的配置核心就一块 server 块:

server { listen 80; server_name your.server.domain; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

try_files那一行是 SPA 部署的精髓——前端路由是浏览器端跳转,刷新后服务器收到的 URL 可能不是实际存在的文件路径,通过try_files把所有请求都指向index.html,让前端路由接管。没有这一行,刷新页面就会出现 404。

5.2 后端打包与进程守护

后端我打成 jar 包部署,启动命令是java -jar author-system.jar --spring.profiles.active=prod。生产环境的数据库连接用单独的application-prod.yml配置,与开发环境的配置隔离,避免把测试库的连接信息带到线上。我用 systemd 写了一个简单的 service 文件做进程守护,进程挂了自动拉起,日志统一输出到指定文件,这个比裸启动一个 java 进程健壮很多。如果公司里有 Docker 环境,也可以打成镜像部署,但单机小应用我仍然推荐 jar + systemd 的轻量方案。

5.3 数据库初始化与常见坑

数据库部分我写了两个脚本:init_schema.sql负责建库建表,init_data.sql负责插入字典数据和默认管理员账号。初始化时要特别注意字符集,数据库、表、字段的字符集都要统一用utf8mb4,否则存"著"这类生僻字或特殊符号时可能出现乱码。连接串里也要显式带上characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai,九个时间相关的坑——时区不配好会导致日期读写差 8 个小时,这是非常容易忽略的。

注意:MySQL 8.0 和 5.7 的驱动类名不同,8.0 用com.mysql.cj.jdbc.Driver,如果你还沿用旧的com.mysql.jdbc.Driver,连接时会直接报错。版本匹配这件事,在部署时排第一。

6. 实战排错记录:五个我耗了最久的问题及完整排查链路

6.1 分页失效:PageHelper 的坑

现象:列表接口返回的数据量是全部而不是分页大小,页面上翻页也不生效。

排查链路:先在浏览器 Network 面板看接口返回的 total 是不是全量数据,确认后端分页逻辑有问题;然后用事务日志查看实际执行的 SQL,发现根本没有 LIMIT 语句。最终定位到原因——Service 层在PageHelper.startPage()之后、查询之前,先调用了一次字典缓存的查询,这次查询吃掉了"当前分页上下文",导致紧接着的作家查询没有被分页。解决方案是把startPage移到目标查询紧邻的位置,中间不能插入任何其他查询。

6.2 中文乱码:从 URL 到数据库的完整链路

现象:前端搜"莫言"查不到,搜拼音首字母可以,且数据库里显示的名字乱码。

排查链路:先确认数据库表字符集是 utf8mb4;再用 Postman 直接请求接口并打印参数,发现参数也是正常的;最后看 Nginx 日志和 SpringBoot 接收到的请求,发现是前端 axios 没有设置Content-Type: application/json;charset=UTF-8,导致 POST 参数按默认编码传输,Spring 解析时按 ISO-8859-1 处理,中文全变问号。这个坑在前后端分离项目里非常经典,本质是编码问题要整条链路一致——前端请求、Nginx 转发、后端读取、数据库存储,任何一环不对齐都会出问题。

6.3 跨域问题:开发环境爽了,生产环境却报跨域

现象:本地开发通过 Vite 代理访问后端一切正常,部署后浏览器控制台报 CORS 错误。

排查链路:开发环境 Vite 代理相当于"后端之间的转发",浏览器不直接接触后端接口,所以没有跨域;生产环境前端静态页面和 API 如果不在同一域名下,就是真正的跨域请求。我的解决方案是生产环境统一走 Nginx 反向代理,前端只请求同源/api路径,从根本上避免跨域,而不是在后端代码里配置@CrossOrigin放行所有来源——那等于给攻击者开了一扇门。

6.4 日期字段整体少 8 小时

现象:录入作家的出生年份、获奖年份后,列表页显示的时间比实际少 8 小时。

排查链路:这个问题的根因几乎只有一个——数据库连接串没配serverTimezone=Asia/Shanghai,MySQL 驱动默认用 JVM 时区解析日期,而我们服务器系统时区是 UTC,导致 8 小时偏移。解决方式是统一连接串时区配置,并保持数据库、JVM、系统三层时区一致。再往深挖的话,Java 8 的LocalDate/LocalDateTime用法也要注意——如果用Timestamp接 JSON 里的日期字符串,类型不匹配也会引发转换错误,这里我统一用LocalDateTime+ Jackson 的格式化注解处理,链路清晰很多。

6.5 导入 Excel 一半成功一半失败

现象:导入 500 条作家数据,提示 300 条成功,200 条失败,但不知道具体失败原因。

排查链路:上线前我把监听器里的错误收集逻辑只写了"第 X 行失败"没有写原因,用户反馈无从下手。后来我调整成收集原始行数据 + 错误原因 + Excel 行号,错误信息精确到"第 102 行:出生年份格式不正确,请填写 4 位数字年份"。这里最大的教训是——批量导入功能的体验好坏,完全取决于错误反馈的颗粒度,失败数据能给到行号和原因,用户才有机会自行修正;如果只给一个"导入失败"的提示,这个功能基本等于没做。

7. 这套系统还能怎么扩展:从信息管理走向数据价值

写完这套系统之后,我复盘了一下,有几个方向值得继续做。

一个是把"作家-作品-获奖"的数据自动关联起来,比如录入获奖记录时输入作品名称,系统自动从作品表里匹配并建立关联,减少手选成本。另一个是增加全文检索能力——作家简介、作品简介这些长文本,MySQL 的LIKE已经撑不住了,可以引入全文索引或轻量级的检索引擎做内容搜索。还有一个是数据大屏,把获奖趋势、流派分布、地域分布这些统计指标直观地投到展示屏上,这套系统里数据库设计和接口设计都已经预留好了统计口径,接大屏只是时间问题。

这套系统从设计到落地大约花了两周,一半时间在写业务代码,一半时间在跟各种"环境问题"缠斗。但恰恰是这些坑让我对前后端分离、MyBatis 动态 SQL、部署链路这些事有了比书本上深刻得多的理解。如果你也要做一套类似的作家信息管理或者更广义的资料管理系统,希望这份记录能帮你少踩几个坑。尤其记住两件事:数据库设计时想清楚检索维度,接口设计时统一返回结构,这两个底子打好了,后面所有页面都顺手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询