手上最近在做一套高校教师电子名片系统,选型是Java SpringBoot + Vue3 + MyBatis,数据库用的MySQL,前后端完整分离。这套东西本身不算复杂,但麻雀虽小五脏俱全,从需求拆分、表结构设计、接口约定到前后端联调、打包部署,整个链路走下来,踩坑不少,收获也实在。这篇博客就把这套系统从零到一的完整实现拆开讲清楚,适合正在做毕设、想练手前后端分离项目,或者准备把SpringBoot+Vue3这套组合用进实际工作的同学。
先把这个项目是什么一句话讲明白:高校教师电子名片系统,就是把线下纸质名片、教师简介页搬到线上,管理员在后端维护教师信息、院系归属、研究方向、代表性成果,访客在前端按院系、按姓名搜索浏览教师名片。它不像电商、办公系统那样业务复杂,但恰好覆盖了管理系统最典型的增删改查、关联查询、分页搜索、权限控制,是练手前后端分离的极佳样本。
这套技术栈的搭配也是非常主流的:SpringBoot负责REST接口,MyBatis负责SQL映射,Vue3 + Vite负责页面交互,Axios负责请求,MySQL负责持久化。下面我从项目设计到落地实现,把关键环节逐一展开。
1. 项目整体设计与技术选型拆解
1.1 高校教师电子名片系统的需求核心是什么
做任何项目先别急着写代码,需求拆清楚,后面能少走一大半弯路。教师电子名片系统表面看是个"展示名片"的小应用,但实际拆开,它包含三个端:访客(浏览名片的普通用户)、管理员(后台维护数据的操作者)、系统本身(承载数据和业务逻辑的引擎)。
从功能层面拆,核心要解决四件事:
- 名片数据的结构化管理:教师姓名、职称、所在院系、研究方向、邮箱、办公地点、个人简介,这些是名片的基础字段。除了这些,通常还会挂上代表性论文、承担项目、获奖情况这类附加内容。
- 多条件检索:用户要能按院系筛选、按姓名或研究方向搜索,这涉及后端组合查询和分页,是后端基本功的集中体现。
- 后台数据维护:管理员要能对教师信息做新增、编辑、删除,对院系统计和维护,对用户进行管理。这是我做这套系统时花时间最多的部分。
- 展示与交互:访客端要有清晰的名片卡片布局,点击详情能展开完整信息;后台要有表格化的管理界面,操作友好、响应快。
打个比方,这个项目就像做一个"教师版通讯录Plus"。你可以理解为:普通通讯录只有姓名和电话,而电子名片系统在这个基础上增加了研究方向、学术成果、个人主页等展示型信息,并且全部通过Web界面在线管理,不再依赖纸质通讯录的打印和分发。
1.2 为什么选SpringBoot + Vue3 + MyBatis这套组合
技术选型是每写一行代码前都必须想清楚的事。这套组合放在2024-2025年的国内开发环境里,依然是高性价比的选择,没有之一。
先看后端。SpringBoot解决的是"接口服务怎么快速搭起来"的问题。它的自动配置让项目不需要繁琐的XML配置就能跑起来,内嵌Tomcat让部署变成"一个jar包扔上去",对中小型系统和毕设场景都极其友好。配合Spring MVC的注解式路由,一个Controller里就能把REST接口写得很干净。
再看持久层。MyBatis相比JPA/Hibernate,最大的优势是SQL可控。教师名片的列表页往往需要动态拼接搜索条件,比如"按院系过滤 + 按姓名模糊查 + 按研究方向模糊查",这种多条件组合查询,MyBatis的<if>标签配合XML映射文件写起来非常顺手,SQL长什么样、怎么优化,完全在你掌控里。
前端为什么是Vue3?坦诚说,Vue2已经进入维护末期,新项目再用它不太明智。Vue3的组合式API(Composition API)让逻辑复用变得更简单,配合Vite开发时的热更新速度比Webpack快好几个量级,开发体验完全不一样。对于这套系统来说,Element Plus组件库和Vue3配合得相当丝滑,后台管理界面基本是"搭积木式"开发。
MySQL在数据库选型上没有悬念。开源、成熟、文档多、会的人多,一个几十张表以内的中小系统,MySQL配合InnoDB引擎和utf8mb4字符集,性能和数据安全性完全够用。
这套组合背后还有一个很实际的考虑:招人好招,资料好查,排错好搜。遇到中文报错,社区里几乎都能找到答案,对学习者和维护者都友好。
1.3 前后端分离架构到底解决了什么问题
很多同学最初做Web项目喜欢用"前后端不分离"的模板渲染方式,例如后端直接把Thymeleaf页面Render出来。等做完这套名片系统,我对"前后端分离"这四个字有了更实在的理解。
前后端分离的核心不是"把代码分成两个文件夹",而是以下三点:
- 关注点分离:前端负责页面渲染和交互,后端负责数据校验、业务逻辑和持久化。两边的代码不互相掺和,前端改动不需要重启Java应用,后端换逻辑不需要动页面模板。
- 接口契约化:前后端通过HTTP + JSON通信,接口URL、请求参数、响应体结构就是双方的契约。前端可以Mock数据并行开发,不依赖后端接口就绪。
- 部署解耦:前端构建出的静态资源(HTML/CSS/JS)可以放到Nginx,后端jar包独立运行在服务端。两边各自扩容、各自升级,互不影响。
用个生活化的类比:不分离就像在同一个厨房里,洗菜、切菜、炒菜都是同一个人,虽然不用传递订单,但人一忙起来全都卡在一起;分离之后,厨房里有人专门洗菜备菜,有人专门掌勺炒菜,中间靠一份写清楚的菜单(API文档)协作,效率完全不一样。
2. 名片系统的数据模型设计思路
2.1 MySQL表结构设计的前置思考
数据模型是系统的地基,表设计得合理,业务层和接口层的开发会顺很多;设计得别扭,后面写Mapper时到处是奇怪的关联和冗余。我在动手建表前,先画了一遍实体关系,理清楚:
- 教师(teacher)属于哪个院系(department),这是多对一关系。
- 教师的研究方向、教育经历这类信息,属于一对多关系。
- 教师的论文成果、项目课题,可以单独建表,也可以压缩为JSON字段。
对于名片系统这种场景,我的取舍是:核心字段单一化 + 附加信息灵活化。什么意思?教师名片上最常展示、需要搜索和排序的字段(姓名、职称、院系、学历、研究方向)单独建列;而荣誉、社会兼职这类低频、不需要检索的字段,用一个统一的"荣誉/经历"表或者直接在详情接口中聚合返回。
2.2 核心数据表结构说明
最终我设计了四张核心表:院系表(department)、教师表(teacher)、用户表(sys_user)、教师成果表(teacher_achievement)。下面给出可以直接参考的表结构设计:
-- 院系表 CREATE TABLE `department` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `dept_name` varchar(64) NOT NULL COMMENT '院系名称', `dept_code` varchar(32) DEFAULT NULL COMMENT '院系编码', `sort_order` int(11) DEFAULT '0' COMMENT '排序', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='院系表'; -- 教师表 CREATE TABLE `teacher` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `teacher_no` varchar(32) NOT NULL COMMENT '教师工号', `name` varchar(32) NOT NULL COMMENT '姓名', `title` varchar(32) DEFAULT NULL COMMENT '职称', `degree` varchar(32) DEFAULT NULL COMMENT '学历', `dept_id` bigint(20) DEFAULT NULL COMMENT '所属院系ID', `research_area` varchar(255) DEFAULT NULL COMMENT '研究方向', `email` varchar(64) DEFAULT NULL COMMENT '邮箱', `office_location` varchar(128) DEFAULT NULL COMMENT '办公地点', `bio` text COMMENT '个人简介', `avatar_url` varchar(255) DEFAULT NULL COMMENT '头像地址', `status` tinyint(1) DEFAULT '1' COMMENT '状态 1正常 0隐藏', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_dept_id` (`dept_id`), KEY `idx_teacher_no` (`teacher_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师名片表'; -- 教师成果表(代表性论文/项目/获奖) CREATE TABLE `teacher_achievement` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `teacher_id` bigint(20) NOT NULL COMMENT '教师ID', `type` tinyint(1) DEFAULT '1' COMMENT '类型 1论文 2项目 3获奖', `title` varchar(255) NOT NULL COMMENT '成果名称', `description` text COMMENT '成果描述', `achievement_time` varchar(32) DEFAULT NULL COMMENT '时间', PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师成果表';sys_user表不再展开细说,基本就是id、username、password(BCrypt加密)、role这几个字段。需要注意一点,无论用什么框架,密码都不能明文存储,这是安全底线。
2.3 MyBatis层的设计要点与Mapper实战
表结构定完,就进入MyBatis层。这套系统里最有代表性的查询是教师分页加组合条件查询,我在Mapper里用动态SQL实现,这也是MyBatis最核心的价值之一。
看一下教师列表查询的Mapper接口:
public interface TeacherMapper { List<TeacherVO> selectTeacherPage(@Param("name") String name, @Param("deptId") Long deptId, @Param("researchArea") String researchArea, @Param("offset") Integer offset, @Param("limit") Integer limit); Long countTeacherPage(@Param("name") String name, @Param("deptId") Long deptId, @Param("researchArea") String researchArea); }对应XML里的动态SQL:
<select id="selectTeacherPage" resultType="com.example.vo.TeacherVO"> SELECT t.id, t.teacher_no, t.name, t.title, t.degree, d.dept_name AS deptName, t.research_area, t.email, t.office_location FROM teacher t LEFT JOIN department d ON t.dept_id = d.id <where> <if test="name != null and name != ''"> AND t.name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null"> AND t.dept_id = #{deptId} </if> <if test="researchArea != null and researchArea != ''"> AND t.research_area LIKE CONCAT('%', #{researchArea}, '%') </if> </where> ORDER BY t.create_time DESC LIMIT #{offset}, #{limit} </select> <select id="countTeacherPage" resultType="java.lang.Long"> SELECT COUNT(*) FROM teacher t <where> <if test="name != null and name != ''"> AND t.name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null"> AND t.dept_id = #{deptId} </if> <if test="researchArea != null and researchArea != ''"> AND t.research_area LIKE CONCAT('%', #{researchArea}, '%') </if> </where> </select>这里有一个很容易踩的坑:LIKE CONCAT('%', #{value}, '%')不要直接在XML里写'%${value}%',后者会引入SQL注入风险。用#{}是预编译占位,${}是字符串拼接,这是写MyBatis的基本安全常识。
另一个要点:分页时先count再查list,这是标准姿势。有些新手喜欢只写一个limit查询,然后在前端用data.length判断有没有下一页,这种设计在小数据量下能跑,但数据一多就出问题。尤其名片系统的教师数量虽然不算海量,但"先查总数再分页取数据"的习惯要从一开始就养成。
3. 前后端分离实战:接口设计与关键实现
3.1 RESTful API接口约定
接口是整个前后端协作的桥梁。我做这套名片系统时,接口设计遵循了几个明确约定,这里可以直接抄作业:
- 统一响应体结构:所有接口返回
{ "code": 200, "message": "success", "data": ... },前端Axios拦截器统一处理code,省去每处判断。 - 资源用名词复数命名:
/api/teachers、/api/departments,通过HTTP方法区分动作:- GET
/api/teachers/{id}获取单个 - GET
/api/teachers?page=1&size=10&name=张分页列表 - POST
/api/teachers新增 - PUT
/api/teachers/{id}编辑 - DELETE
/api/teachers/{id}删除
- GET
- 管理端接口加
/admin前缀并做权限校验,访客端接口走/public前缀或不做鉴权。
Out of the box,SpringBoot + Spring Security的过滤链里做接口鉴权并不复杂,但如果您想轻量化实现,也可以在Interceptor里写一个简单的token校验。我在这个项目里用了JWT做登录态管理,后端在登录成功后签发一个token,前端每次请求在Header里带Authorization: Bearer <token>。后端用一个拦截器校验token并解析出用户角色,再进行接口访问控制。
3.2 SpringBoot后端核心模块实现拆解
SpringBoot后端的层次结构我划分得很清爽:Controller(接收参数、返回响应)→ Service(业务逻辑)→ Mapper(数据访问)。不建议再套过多复杂的设计模式,对于这种体量的系统,YAGNI原则优先。
教师名片详情的接口实现逻辑是这样的:
@RestController @RequestMapping("/api/teachers") public class TeacherController { @Autowired private TeacherService teacherService; @GetMapping("/{id}") public Result<TeacherDetailVO> detail(@PathVariable("id") Long id) { TeacherDetailVO detail = teacherService.getTeacherDetail(id); return Result.success(detail); } }Service层里做的事情包括:查教师基本信息、查教师成果列表、将实体字段转换为VO对象、处理头像地址拼全。聚合查询这种场景,用两个Mapper查询然后在Service层组装,要比写一个多表嵌套查询的大SQL更清晰、更容易维护。
头像上传是名片系统一个常见功能。我使用的是本地存储方案:文件上传接口接收MultipartFile,将文件保存到服务器某个目录(如/data/avatar/),同时把访问URL返回给前端。这里有一个生产环境必须注意的问题:不要将上传目录暴露为静态资源根目录,而是通过单独映射或者对象存储来做访问控制。个人项目为了简便,可以在配置类里加一个资源映射,把磁盘路径映射到/upload/**虚拟路径。
3.3 Vue3前端核心实现:组合式API与组件设计
Vue3前端部分,我用Vite初始化的项目,目录结构按"页面-视图-组件"三层组织。核心页面是教师名片列表页、教师详情页、后台管理页(教师编辑、院系管理、系统登录)。
Vue3的组合式API比Vue2的Options API更贴合逻辑复用的场景。以名片列表页为例,核心代码可以拆成三块:查询条件(响应式ref)、接口请求(onMounted或watch触发)、列表渲染(v-for + 卡片样式)。
有代表性的逻辑是搜索防抖。访客在名片系统的搜索框里输入关键字,如果不做防抖,每敲一个字母就向后端发一次请求,数据库压力大且响应也不连贯。我用一个轻量的防抖实现:
import { ref, watch } from 'vue' import { searchTeachers } from '@/api/teacher' const keyword = ref('') const pageData = ref([]) let timer = null watch(keyword, () => { clearTimeout(timer) timer = setTimeout(() => { fetchList() }, 300) }) async function fetchList() { const params = { name: keyword.value, page: currentPage.value, size: 10 } const res = await searchTeachers(params) pageData.value = res.data.records }写到这里我想强调一个Vue3最佳实践:数据状态的变更尽量放在方法里,避免在模板中处理过多逻辑。很多新手喜欢在{{ }}里写三元表达式,临时用一下问题不大,一旦逻辑变复杂应立即抽取为计算属性computed或独立方法。名片卡片上要显示"职称-学历"拼接、研究方向的截断等,这些我都抽成了computed或函数,模板部分保持干净。
3.4 跨域与会话管理的实战配置
前后端分离项目绕不开跨域问题。开发环境中,前端Vite跑在5173端口,后端SpringBoot跑在8080端口,两个端口不同就存在跨域。解决方案有两种:后端开启CORS,或者前端用Vite代理。
我推荐开发环境用Vite代理,它更贴近生产部署的真实链路:
// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })生产环境则用Nginx做反向代理,把/api前缀的请求转发到后端服务。这样的好处是:前端代码中不用写死后端地址,统一走相对路径/api/xxx,部署时灵活。
开发时有一个特别容易困惑的坑:Vite代理配置里changeOrigin一定要设为true,否则后端拿到请求的Host头不对,某些框架的过滤器会误判来源。我当时调试时这个字段漏掉,导致登录接口一直拿不到前端传过来的Cookie,浪费了不少时间。
登录态管理我用JWT,它不是传统意义上的Session,而是服务端无状态的。每次请求在Axios拦截器里注入token:
axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config })响应拦截器中统一处理401,跳转登录页,这个机制保证前后端的鉴权跳转逻辑在一个地方维护,不会散落各处。
4. 环境搭建、部署上线与常见问题排查
4.1 从零搭建开发环境的关键步骤
把这套系统的开发环境跑起来,需要准备的组件如下:
- JDK 1.8或11(我用的JDK 8,稳妥)
- Maven 3.6+
- Node.js 16+(Vite 4要求)
- MySQL 8.0
- Redis选装(如果用Redis做缓存或Session,名片系统可不用,先省掉)
新建数据库并导入SQL脚本后,修改后端application.yml中的数据源配置:
spring: datasource: url: jdbc:mysql://localhost:3306/teacher_card?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case这个配置必须打开。默认情况下,MySQL字段是dept_id这种下划线风格,而Java实体类字段是deptId这种驼峰风格,不开启映射会把所有字段查成null。这个配置看似微小,实则是MyBatis使用中最常见的坑之一。
后端启动后,访问http://localhost:8080/api/teachers检查接口是否通。前端npm install后启动,访问http://localhost:5173,页面能加载数据就说明联调通过。
4.2 部署到服务器:Jar包 + Nginx的实操记录
开发环境跑通只是第一步,最终系统要部署到服务器上。整个过程我记录一下关键决策。
后端打包使用Maven:
mvn clean package -DskipTests执行完会在target/目录下生成一个可执行jar包,用nohup java -jar teacher-card.jar > app.log 2>&1 &启动。要重点关注:生产环境不要使用IDE直接启动,jar包方式可以配合systemd守护进程,进程崩溃后还能自动拉起。
前端构建:
npm run build构建后生成dist/目录,里面有静态资源。将它放到Nginx的html目录,并配置Nginx转发/api到后端:
server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files这一行是SPA路由的关键。Vue3的history路由模式下,前端路由如/teachers/1在服务器上并没有真实文件,如果不加try_files配置,刷新页面就会404。当初我没配这一行,部署完成后所有一级页面正常,但一到详情页刷新就报404,排查了半天才发现是Nginx配置里漏掉了它。
4.3 常见问题与排查技巧实录
把开发这套名片系统过程中遇到的高频问题整理成速查表,希望能帮你少走弯路。
| 问题现象 | 可能原因 | 排查思路与解决 |
|---|---|---|
| 前端请求接口404 | 请求路径与后端Controller路由不一致 | 打开浏览器Network面板,对比URL和@RequestMapping中的路径;注意上下文路径是否设置 |
| 查询出来的教师deptId为null | 表字段与Java属性映射失败 | 检查mybatis配置map-underscore-to-camel-case是否开启,或者SQL中是否写了别名 |
| 登录后访问管理接口返回401 | token没传或已过期 | 检查Axios拦截器是否注入Authorization头,检查JWT过期时间设置 |
| 前端能打开但接口返回CORS错误 | 代理配置或后端CORS配置不正确 | 开发环境优先使用Vite proxy;生产环境检查Nginxproxy_set_header配置 |
| 中文乱码 | 数据库连接串缺字符集配置 | URL加上characterEncoding=utf8,建表时统一utf8mb4 |
| 上传头像后页面无法显示 | 静态资源映射未配置或路径不对 | 检查后端资源映射和Nginx静态文件路径,确认存储在磁盘绝对路径是否存在 |
| 数据库连接池耗尽 | 连接没有正确释放或并发太大 | 检查DataSource配置参数max-active、min-idle,查询耗时的SQL加索引优化 |
| MyBatis打印SQL但数据没变 | 事务未提交 | Service层检查是否加了@Transactional;默认READ_COMMITTED隔离级别下update后立即查询应在同一事务内 |
这里面最值得展开的是事务问题。名片系统里新增教师的操作,通常要同时插入teacher表和teacher_achievement表,两步操作必须在一个事务里执行。如果中途第二步出错,第一步的teacher数据就会变成脏数据。在SpringBoot中通过@Transactional注解很容易实现:
@Transactional(rollbackFor = Exception.class) public void createTeacher(TeacherCreateRequest request) { teacherMapper.insert(request.getTeacher()); if (CollectionUtils.isNotEmpty(request.getAchievements())) { request.getAchievements().forEach(a -> { a.setTeacherId(request.getTeacher().getId()); achievementMapper.insert(a); }); } }注意rollbackFor = Exception.class这个属性,默认情况下Spring只对RuntimeException回滚,对受检异常不会回滚。有些新手写事务时没有指定rollbackFor,业务中对某个受检异常吞掉后继续执行,事务提交了回滚却失效,这种问题极其隐蔽。我的习惯是一律写上这个属性。
4.4 系统优化与安全加固的进阶心得
名片系统虽小,但该有的安全和性能意识不能少。下面几条建议,是我结合这套代码在线上运行后的经验总结。
安全方面:
- 密码必须BCrypt加密存储,不要用MD5。MD5现在用彩虹表几秒就能破解,BCrypt自带盐,是目前Spring Security默认采用的方案。
- 管理端接口一定要做权限校验。最简单的方式是设计一个
@RequireAdmin注解,在拦截器中判断登录用户的角色。 - SQL注入防护要落到细节。所有MyBatis动态SQL中,值的地方用
#{},只有表名、排序字段这类结构型内容才考虑${},而且结构型内容要加白名单校验。
性能方面:
- 列表页字段使用
LEFT JOIN时,注意为dept_id建索引,否则关联查询在大数据量下会变慢。 - 教师工号和名字是高频搜索条件,在
teacher_no和name上建索引(我的建表脚本中已包含)。 - MySQL连接串加
useSSL=false时,如果服务器不支持SSL,不写这个参数可能在启动时报警或连接失败。
还有一条开发体验方面的建议:前端接口请求统一收敛到一个api模块里管理,不要在每个组件里直接写Axios请求。比如api/teacher.js里集中导出所有教师相关接口函数,组件里只调用函数。这样接口路径修改时只需要改一个文件,而且配合IDE的智能提示,开发效率和可维护性都能大幅提升。
做名片系统过程中,我自己最大的感悟是:技术本身都简单,真正花时间的往往是"边界情况"——字段为空时的显示、排序不一致、删除关联数据时的约束、刷新页面后的登录态保持。这些细节做好了,系统才真正"能用"。
5. 功能扩展与项目复盘思考
5.1 在名片系统基础上可以怎么扩展
这套名片系统做出来后,后续扩展的空间其实很大。下面说几个我调研过的方向,你可以根据兴趣选。
最贴近现有结构的扩展方向是教学与科研方向的深度展示。比如在教师详情页增加课程信息,展示教师承担的教学课程、授课时间;也可以接入论文数据,按年份聚合展示发表记录和研究领域标签。这类扩展基本不需要修改表的主结构,新增几张关联表即可。
第二个方向是权限细粒度控制:当前系统只有管理员和访客两种角色,如果要支持系所秘书自主维护本系教师信息,就需要在数据权限上再加一层过滤。例如在Spring Security的AccessDecisionManager中注入"数据所属院系"的校验逻辑。
第三个方向是前后端能力升级:例如把Vue3的TypeScript改造提上日程。TypeScript能显著提升大型前端项目的健壮性,联调时的字段拼写错误在编译阶段就能发现;后端引入MyBatis-Plus,把常见单表查询的样板代码进一步压缩。注意,MyBatis-Plus虽然好用,但它改变不了复杂多表查询必须要写XML的现实。
这些扩展方向中,如果只挑一个对新手最友好的,我推荐第一个:课程信息与科研成果展示。它不需要引入新的技术栈,也不需要改动现有核心表,只需要新增表、新增接口、新增前端展示组件,完整串一遍,对理解"表设计怎么跟着需求演进"非常有帮助。
5.2 做一个完整的独立项目,真正学到什么
从一个纯粹学习者角度,这套名片系统项目教会的不仅是技术框架,更是"从需求到落地的完整工程化思维"。
首先是"接口先行"的开发习惯。我在做名片系统的过程中,先把所有需要的接口列成清单,明确路径、入参、出参、鉴权要求,才开始动手写前后端代码。实际效果是:前后端开发可以并行推进,后端写接口的同时前端用Mock数据渲染页面,最后对接时几乎没有返工。
其次是"拆分-收敛"的设计能力。一个看似简单的名片系统,拆成数据层、服务层、接口层、页面层、组件层,每一层各司其职,这比"从头到尾一气呵成"的编码方式更容易维护和调试。哪怕项目很小,也要养成这种分层思维。
最后是对排错工具的熟练。做前后端分离项目,Chrome DevTools Network面板和Vue DevTools是必开的两样东西。几乎所有联调问题都能通过它们定位到是"前端没发请求"、"后端报500"还是"响应数据不符合预期"。一旦熟练使用这套排查链路,定位bug的效率会提升数倍。
5.3 最后一小段:我对这套技术栈的实际体会
如果让我用一句话总结这套系统的开发体验:SpringBoot + Vue3 + MyBatis这套技术栈,是当前做中小型管理系统最"省心"的组合,没有之一。它也许不够"新",不像微服务架构那样炫,但胜在稳定、资料多、生态成熟,对一个人从零开发一个完整项目非常友好。
我在实际写完这套代码后,最满意的地方不是用了什么新技术,而是整个项目保持了一致的编码风格和清晰的模块划分。如果有同学正在用类似题目做毕设,我建议你在功能性之外,多花一点精力在代码组织和注释规范上,这会让你在答辩时有更多可以展示的"工程素养"。名片系统本身的业务并不难,真正让你脱颖而出的是能不能把一个简单的系统做出规整、可靠、可维护的感觉。这也是完全独立开发一个项目比单纯看教程学框架更能锻炼人的原因。