我的毕业设计选了“基于Spring Boot的农村老人个人信息管理系统”这个题目。说实话,刚开始我有点看不上它:班里同学做的多是点餐系统、预约挂号、电商后台这类“看起来有排面”的项目,而“老人信息管理”这个名字光听着就感觉平平无奇。但真正动手以后我才发现,这类系统对数据完整性、字段扩展性、多角色协同的要求比想象中高得多,而且农村场景里还有不少特殊细节,处理不好后续开发会非常难受。
如果你也在准备类似的毕业设计,或者导师给了你一个“看起来普通、实际上到处都是坑”的信息管理类题目,那么这篇博客应该能帮到你。我把自己从选题、需求拆解、数据库设计、编码实现,再到打包部署和答辩准备的全过程都整理了出来,所有踩过的坑,能避的就提前避。
1. 看似普通的“老人信息管理”,拆完需求才发现深不见底
很多同学拿到“XX管理系统”这个题目,第一反应就是上网找一套通用后台管理系统的源码,改改菜单、换换字段就当作完事了。我一开始也动过这个念头,但后来我的导师提醒了一句:你要先搞清楚这个系统到底是给谁用的,对方拿着它要完成什么日常工作。顺着这个思路拆了一遍需求,我才发现这个题目真的不简单。
1.1 农村场景里到底有哪些数据需要“管”起来
城市里的老年人信息,通常是社区网格员或养老机构在做维护,数据相对集中。但农村不一样,老人的居住地分散,可能一个村有十几个片区,居住在山脚下的、河对面的都有。村委会工作人员需要掌握的信息远不止“叫什么、住哪里、电话多少”这么简单。
我梳理下来,这个系统至少需要覆盖四类核心数据:
第一是老人基础档案:姓名、性别、出生日期、身份证号、户籍地、实际居住地、联系电话、婚姻状况、居住情况(独居、与配偶同住、与子女同住、住养老机构)、主要生活来源等等。别看这些字段简单,任何一个在后续业务里都可能成为筛选条件,漏掉就会非常被动。
第二是健康档案:既往病史、慢性病种类、用药情况、近期的体检记录、护理等级、是否失能、是否有残疾等。这部分数据是动态增加的,一个老人可能每隔半年就有一条新的体检记录,系统不能只存一张静态表,必须做成时间轴式的记录链。
第三是日常探访与服务记录:村干部或志愿者什么时候上门探访过、聊了什么、老人反馈了什么问题、有没有提供助餐、助洁、代买代办等生活服务。这类记录是后续判断老人生活质量的重要依据,也最能体现这个系统的“人情味”。
第四是补贴与保障信息:老人享受了哪些补贴项目、每月补贴金额是多少、当前是否正在发放、中间有没有停发或变更。这些信息需要留痕,最好还能追溯每个状态的变更时间。
说白了,这类系统的难点不在于单个功能有多难实现,而在于同一个老人在不同业务里都有一条记录线,你要把这些线串起来。刚开始我把这些业务都想明白了之后,自己是有被吓一跳的:这不就是一个小型的“以人为核心”的客户关系管理系统吗?
1.2 角色权限梳理:哪些人用这个系统,各自能干什么
需求梳理的第二步,就是搞清楚谁会打开这个系统。我只定了三个角色加一个查看端:
管理员负责系统全局配置,比如维护用户账号、数据字典、村组片区信息,也可以查看所有老人的数据和统计报表;村工作人员是使用频率最高的角色,负责录入和更新老人档案、填写探访记录、登记服务信息;医生或护理人员只看健康相关模块,他们的主要操作是录入体检数据和护理评估;家属查看端则只允许查看自家老人的基本信息和近期服务记录,这个在毕业设计里可以不做完整App,做一个微信端或者网页端只读页面就行。
角色一分开,功能权限和“数据权限”的问题就来了。比如村工作人员应该只能看到自己负责那个村的数据,而不能跑到隔壁村去翻档案。这个需求实现起来并不只是前端按钮隐藏这么简单,后端查询SQL必须根据当前登录人的归属范围动态拼接条件。后面我会专门讲到这部分怎么实现。
1.3 我最终敲定的系统功能边界
考虑到毕业设计的时间和精力,我没有把功能做得特别庞大,最终确定的模块包括:
- 系统管理模块:用户管理、角色管理、菜单权限管理、数据字典管理
- 老人档案模块:新增、编辑、详情查看、组合条件查询、身份证唯一性校验
- 健康档案模块:体检记录、慢病随访记录、用药记录的管理
- 探访服务模块:探访计划的登记、探访结果的录入、服务记录查询
- 补贴管理模块:补贴项目的配置、补贴记录登记、发放状态变更
- 记录统计模块:按年龄、健康等级、居住情况等维度展示统计图表
- 文件管理:体检单、申请表、身份证照片的上传和回显
这个边界既保证了业务链路的完整性,又没有把自己耗死在细枝末节里。我强烈建议,做毕业设计第一步不是写代码,而是先画功能清单,跟导师确认边界。边界一旦扩大,后期一个月都填不完那个坑。
2. 技术选型不是跟风:为什么是Spring Boot而不是其他框架
“Spring Boot做管理系统”几乎是毕业设计圈子里最常见的选择了。但常见不等于不需要思考,我自己选型的时候是认真计算过投入产出比的。
2.1 我用的整套技术栈与版本组合
最终我敲定的技术栈是:
后端使用Spring Boot 3.2 + Java 17 + MyBatis Plus 3.5 + Redis + MySQL 8;接口文档用Swagger/Knife4j;权限控制用Spring Security框架但做了精简配置,加上自定义拦截器做数据权限;前端使用Vue 3 + Element Plus + Vite;文件上传使用本地存储目录加静态资源映射;Excel导入导出使用EasyExcel。
这套技术栈属于典型的“稳妥型组合”。没有特别冷门的东西,遇到问题搜索就能找到答案。版本组合上我提醒一句:Spring Boot 3和2的差异不小,很多教程都基于Spring Boot 2.x,如果你用的是3.x,网上那些旧代码可能直接跑不起来,配置类、依赖命名都有变化。我身边就有同学因为版本不适配在项目启动阶段折腾了整整一周。
2.2 为什么不是SSH也不是PHP
很多学长给的模板还是SSH或者SSM的老结构。不是说那些不能做,而是从学习收益来看,Spring Boot把这些繁琐的XML配置、模板引擎集成都自动化了,让你把有限的精力放在业务逻辑上。而毕业设计最需要展示的,恰恰是你对业务的理解和代码组织能力,不是你会不会写web.xml。
也有人问我为什么不用PHP写,成本更低。但从课题名字上看,“基于Spring Boot”本身就是题目的一部分,技术含量和评阅时的认可度都更合适。作为信息管理类项目,Spring Boot的生态足够丰富,MyBatis Plus这种半自动ORM又特别适合写复杂查询和分页。整个开发过程很少需要关注底层容器细节,能让精力集中在业务实现上。
2.3 后端分层结构与公共封装思路
我按照常见的Controller、Service、Mapper三层结构来组织代码。实体类使用MyBatis Plus注解,配合通用Mapper,基础的增删改查几乎不需要手写XML。
公共封装方面我做了三件事:统一返回Result对象,包含code、msg、data三个字段;统一异常拦截,业务异常用自定义BusinessException抛出;分页查询统一封装成PageResult,前端拿到固定结构后就好处理了。
最开始我偷懒,每个接口都返回Map,结果前端每次都要猜里面有哪些key,到了联调阶段效率极低。后来统一封装以后,前后端甚至不需要反复对接口文档,可以直接开工。
2.4 项目初始化时最容易忽略的配置
Spring Boot项目本身好搭建,但有几个配置特别容易漏,漏了的后果就是启动报错或者接口跑到一半才暴露问题。我自己犯过的几个错包括:
数据源的时区参数没配,导致时间字段相差8小时;MyBatis Plus的逻辑删除配置没打开,查询时deleted=1的数据也查出来了;Jackson的日期序列化格式没有统一,返回给前端的时间变成一串时间戳。
这些配置在代码里就几行的事,但足够让人抓狂。我一并写在公共配置里,后面再提到的任何模块,都要先确保这些基础配置是对的,否则排查问题的时候很难定位到具体原因。
3. 数据库设计:比写代码更磨人的是“一个字段定终身”
数据库设计是我在这个项目里花时间最多的部分。写代码可以改,重构起来虽然麻烦但可控,而数据库如果一开始没设计好,后面加字段、改字段、拆表,那才是真正的灾难。
3.1 老人基础档案表的字段设计
我的核心表叫older_person,字段看起来不多,但每一个字段都是经过思考的。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| name | varchar(50) | 老人姓名 |
| gender | tinyint | 性别:1男,0女 |
| id_card | varchar(18) | 身份证号,唯一 |
| birth_date | date | 出生日期,不直接算年龄 |
| phone | varchar(20) | 联系电话 |
| area_code | varchar(20) | 所属片区编码 |
| address_detail | varchar(255) | 详细居住地址 |
| living_status | tinyint | 居住情况:0独居,1与配偶,2与子女,3养老机构 |
| health_level | tinyint | 健康等级:1健康,2慢病稳定,3失能,4重症 |
| source_type | tinyint | 信息来源:0普查录入,1自主登记,2他人代报 |
| deleted | tinyint | 逻辑删除 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
有几个字段容易被忽略,我特别说明一下:
birth_date用date类型而不是字符串,这样后面统计“80岁以上老人”时可以直接用数据库函数判断,但为了查询效率我还是在代码里算了年龄范围再转成日期区间去查。
id_card必须做唯一性校验,这一点在业务里非常重要。我遇到的真实情况是,同一个老人在普查时被录入了一次,后续村干部又照着纸质名册手动录了一次,结果出现两条档案。所以我在数据库层面建了唯一索引,同时用逻辑删除的情况下,还要额外处理“已删除的档案占着身份证号”的问题,这个坑后面专门说。
area_code不要直接存中文村名,而是编码,再用一个专门的area表维护编码和名称的映射。方便后续按片区统计,也方便查询时用编码做前缀模糊匹配。
3.2 健康档案和探访记录的时间轴设计
健康档案我没有单独建复杂的两三张表,而是用一张health_record表,通过record_type字段区分是“体检记录”“慢性病随访”“用药记录”还是“护理评估”。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| older_id | bigint | 老人档案ID |
| record_type | tinyint | 记录类型 |
| record_time | datetime | 记录发生时间 |
| content | text | 记录内容 |
| doctor_name | varchar(50) | 录入人/责任医生 |
| attachment | varchar(255) | 附件地址 |
| create_time | datetime | 创建时间 |
这样设计的好处是,我可以在前端把同一个老人的所有记录按record_time倒序排成一条时间轴,每一段都是老人健康变化的节点。如果想查“最近一次体检记录”,只需要按older_id分组取max(record_time)再关联回原表,逻辑很清楚。
探访服务表visit_record也是类似的思路,不过多加了visit_type(入户探访、电话慰问、志愿活动)和visit_status(计划中、已完成、已逾期)。状态字段用来支持村干部做“本月还没探访的老人”筛选。
3.3 补贴记录里的状态变化
补贴记录让我意识到,很多管理系统里的数据不是“创建之后就固定了”的,而是有生命周期的。比如一条补贴记录,可能是“申请中”“审核通过”“发放中”“已停发”几种状态。
所以我在补贴表里加了status字段和一个change_log表。每次状态变更都往change_log里插一条记录,记录操作人、操作时间、变化前后的状态。这样答辩的时候评阅老师问起“这个数据怎么保证可追溯”,你就有实际的设计可以讲了,而不只是嘴上说“我们做了留痕”。
3.4 通用字段、数据字典和索引规则
所有业务表我都统一加了create_time、update_time、deleted、create_by这几个字段,这是从企业开发习惯里学来的。整套系统下来,后面的统计分析和逻辑删除处理都会轻松很多。
数据字典我也单独建了一张表dict_type和dict_data,用来维护性别、健康等级、探访类型这些枚举值。用字典表而不是写死在代码里的好处是,业务人员可以自己改,不需要改后端发布版本。
索引方面,我在older_person表的id_card上建了唯一索引,在health_record和visit_record表的older_id上建了普通索引,在补贴表的status字段也建了索引。索引不贪多,够查询用就行,滥用索引反而影响写入性能。
4. 业务模块实现回顾:这些个功能是真的要花时间
数据库设计完了,接下来就是编码。很多同学以为后台管理系统的业务模块就是套模板“增删改查”,但自己写起来才发现,真正花时间的往往是那些“看着简单”的细节。
4.1 老人档案的组合条件搜索
第一个功能就是老人档案管理。页面上一排筛选条件:姓名、身份证号、所属片区、健康等级、居住情况,还有一个关键词搜索框。
后端实现我用的MyBatis Plus的LambdaQueryWrapper,动态拼接条件:
LambdaQueryWrapper<OlderPerson> wrapper = Wrappers.lambdaQuery(); wrapper.like(StringUtils.isNotBlank(name), OlderPerson::getName, name); wrapper.eq(StringUtils.isNotBlank(idCard), OlderPerson::getIdCard, idCard); wrapper.eq(healthLevel != null, OlderPerson::getHealthLevel, healthLevel); wrapper.eq(StringUtils.isNotBlank(areaCode), OlderPerson::getAreaCode, areaCode); wrapper.orderByDesc(OlderPerson::getCreateTime);刚开始我犯过一个错:把StringUtils.isNotBlank(name)写在条件外面,然后每次查询都生成不同的SQL,前端传空值时总把不该过滤的条件也拼进去。后来养成习惯,所有条件都是用condition ? 表达式 : 默认的写法,就再没出过类似问题。
还有一个细节是姓名的模糊搜索。用户可能输入的是“张”,也可能是“张三”,中国姓名本身没有空格,直接like就够。但如果是搜索地址,那就得考虑老人不会填标准地址的情况。所以我后来把关键词搜索改成同时对name、phone、id_card最后四位、address_detail做模糊匹配,这样村干部只要记得老人身份证后四位就能查到档案,实际使用下来非常顺手。
4.2 最新健康记录怎么准确取到
健康档案接口里有个需求:列表页要显示出每个老人“最近一次健康评估的状态”。一开始我用的是最笨的办法,在查出老人分页列表后,循环每条数据再去查健康表的最新记录。这也就是传说中的N+1问题,数据量小的时候没感觉,一旦数据上百条,页面响应能慢到2秒多。
后来改成子查询关联,一条SQL解决:
SELECT hr.* FROM health_record hr JOIN ( SELECT older_id, MAX(record_time) AS max_time FROM health_record WHERE older_id IN (...) GROUP BY older_id ) t ON hr.older_id = t.older_id AND hr.record_time = t.max_time用这个思路,列表查询直接从循环几十次变成一次批量查询。答辩的时候如果被问到“你是如何做性能优化的”,这就是一个现成的、可以说清楚来龙去脉的例子,比背一堆八股文要有说服力得多。
4.3 文件上传:身份证照片和体检单怎么存
老人在建档的时候需要上传身份证照片,健康档案里可能要上传体检单图片。我选的方案是本地目录存储+静态资源映射,没有依赖额外的OSS服务,因为毕业设计环境里不需要那么多的扩展性。
具体做法是,在配置文件里指定一个upload.path,例如E:/project/older-upload/,文件上传后端按日期分目录存储,文件名用UUID重新生成,避免中文文件名乱码和重名覆盖。然后把upload.path注册成静态资源映射:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); }这个方案有一个坑:如果你用的是相对路径,idea里运行和打包成jar之后运行,工作目录不一样,文件路径就很容易对不上。我的建议是在application.yml里写死一个绝对路径,或者每秒通过配置项注入,不要用相对路径图省事。踩过这个坑之后,我现在做项目一律都用绝对路径。
4.4 用EasyExcel做批量导入导出
村干部手里很可能有一份Excel名册,几百个老人的信息都在表格里,总不能让工作人员一条条手动录入吧。所以系统里做了一个批量导入的功能,支持上传Excel后自动解析,并按身份证号去重,导入完成后显示“成功多少条、失败多少条、失败原因是什么”。
我用的是EasyExcel,它比POI使用友好很多,一个监听器就能处理数据。
@Slf4j public class OlderPersonExcelListener extends AnalysisEventListener<OlderPersonExcelVO> { private final List<String> errorList = new ArrayList<>(); private final List<OlderPersonExcelVO> validList = new ArrayList<>(); @Override public void invoke(OlderPersonExcelVO data, AnalysisContext context) { if (StringUtils.isBlank(data.getIdCard())) { errorList.add("第" + context.readRowHolder().getRowIndex() + "行:身份证号为空"); return; } validList.add(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 批量保存 } }这里我吃了不少苦头。第一是Excel模板的表头必须和前端下载的模板完全一致,否则解析出来的全是null;第二是导入的日期格式,Excel单元格里的日期可能是“1990/5/1”,也可能是“1990-05-01”,还可能是数字格式的Date序列号,EasyExcel默认处理起来容易出问题。最后我统一先转成字符串,再自己格式化。
5. 权限控制和联调阶段的真实教训
如果整个项目让我只说一个最“值得讲”的部分,我会选权限控制。因为老人信息属于个人敏感信息,系统必须保证不同角色只能看到自己该看的范围。
5.1 数据权限和按钮权限的前后端配合
按钮权限我直接用菜单表加角色菜单关联表控制,前端根据当前用户拥有的权限标识来决定是否渲染某个按钮,比如“新增”“删除”“审核”这些按钮都是有标识的。
数据权限的处理更关键。我在后端定义了一个自定义注解@DataScope,业务查询方法上加上这个注解,然后通过AOP解析当前登录用户:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataScope { String areaColumn() default "area_code"; }AOP里根据用户角色,如果是管理员,就不做任何限制,直接放行;如果是村工作人员,就取出他管辖的area_code列表,拼进查询条件里;如果是健康档案录入人员,限制只能查健康相关表。
这个设计在答辩时非常加分。因为很多管理系统做权限就只是“能不能访问这个模块”的粗粒度控制,而我把数据范围控制在行级别,评阅老师一听就懂,但又感觉你确实想把事情做好。
5.2 与前后端“约定不一致”导致的接口问题
联调阶段是我崩溃次数最多的时候。整理一下,有四个特别典型的教训:
第一个是日期格式。后端date类型返回给前端默认是“yyyy-MM-dd”,但是datetime默认返回的是“2024-06-01T12:00:00”这种带T的格式。Element Plus的日期组件如果只按“yyyy-MM-dd HH:mm:ss”解析,就会直接显示无效日期。最后我统一在配置里加了一个Jackson全局格式化,才消停了。
第二个是跨域。开发环境前端跑在5173端口,后端跑在8080端口,axios直接请求肯定被浏览器拦截。我一开始在后端加了很多允许跨域的配置,后来发现更省事的是前端在Vite里配proxy,所有/api开头的请求都代理到后端,生产环境再走反向代理,开发阶段完全不需要跨域配置。
第三个是上传文件大小。Spring Boot默认单文件上传上限是1MB,我传体检单图片传一张崩一张。需要在配置里显式设置:
spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=20MB第四个是逻辑删除和唯一索引的冲突。这是我上面就提到的坑:我用deleted字段做逻辑删除,同时id_card建了唯一索引。结果删除一条档案后再重新录入同一个老人时,数据库报“Duplicate entry for key id_card”。因为那条数据其实还在表里,只是逻辑上被隐藏了。
这个问题的解决方案一般两种:第一种是真的物理删除,但如果要考虑审计,不行;第二种是在唯一索引里加入deleted字段,做成“逻辑删除后允许重新录入”的唯一约束。MySQL里直接在(id_card, deleted)上建联合唯一索引也是个办法,但对逻辑删除来说还需要配合新增时把deleted初始化成0。我自己最后采用的是“唯一索引加业务校验”的方案:插入前先查一下is_deleted=1的数据,如果存在就先物理删除残留。
这个坑我建议每位做信息管理系统的同学都提前知道,因为它真的会让你的演示当场翻车。
6. 打包部署与验收演示的完整流程
系统功能做完,还远没到可以“交差”的时候。毕业设计验收是要现场演示的,而现场演示最怕的就是环境问题、数据问题、流程断掉。
6.1 本地测试和远程部署环境
我的项目最终部署在我自己电脑的虚拟环境里,但为了保证演示环境稳定,我做了这么几件事:
数据库用MySQL 8,通过一个初始化SQL脚本一次性创建所有表和初始数据。脚本里包含几个测试账号,管理员一个、村工作人员两个,还预置了二十多个带不同健康等级、片区、年龄段的老人数据。这些数据是我花了半天时间手工构造的,目的就是演示的时候每个筛选条件都有结果,不然当场查出来一个空列表,场面会非常尴尬。
后端的打包方式很简单:
mvn clean package -DskipTests java -jar target/older-manage-system.jar打包之后我做了两轮验证:第一轮在自己电脑上从零开始执行部署脚本,确认每一步都能走通;第二轮把项目发给另一个同学,让他完全不知道内部逻辑的情况下,看着我的部署说明文档,看看能不能一次启动成功。这个做法帮我发现了好几个漏写的配置项。
6.2 典型验收演示脚本
演示不是从头到尾乱点一气,而是像讲一个故事一样,把整个业务闭环走一遍。我的演示脚本是:
先以管理员身份登录,进入系统管理模块,创建一个新的村工作人员账号,并分配权限;退出登录,用新账号登录,进入老人档案模块,添加一位老人的信息,包括一张身份证照片上传;给刚才录入的老人生成一条体检记录,并上传体检单附件;切换到管理员角色,进入统计页面,演示按健康等级、年龄段、片区的统计图和列表;最后导出当前筛选条件下的Excel,再演示导入一个10条数据的Excel表格,看到成功和失败结果。
这个流程走下来大概需要8到10分钟,好处是每个模块都有实际演示,而且前后呼应,评委能看出系统是完整的,不是几个页面拼出来的假项目。
7. 论文写作和答辩准备中的实际经验
代码做完只算完成一半,毕业设计还有论文和答辩这座大山。
7.1 论文大纲怎么组织
我的论文基本结构是:绪论、相关技术介绍、系统需求分析、系统设计、系统实现、系统测试、总结与展望。
最有用的经验是,论文的核心章节不是“实现”,而是“需求分析”和“系统设计”。很多同学论文里放一堆代码截图,但描述不出自己为什么要这样设计数据库、权限逻辑是怎么设计的,结果答辩时被问几个“为什么”就卡壳了。我写论文的时候会刻意在每个设计决策后面写一段“为什么这样做”,比如为什么健康档案用类型区分而不是拆表,为什么用数据字典而不是硬编码。这些内容在答辩时就是你的“防弹衣”。
系统测试章节也不能只写“系统运行正常”。我把测试分为功能测试和兼容性测试,列了一张测试用例表格,每一行写清楚测试编号、测试步骤、预期结果、实际结果。评阅老师特别吃这一套,至少看上去你的态度是认真的。
7.2 答辩时大概率会被问到的问题
我根据自己答辩和旁听其他同学答辩的经验,整理了几个高频问题:
“为什么选择Spring Boot?”要答到点上:相比传统Spring,它简化了配置、内嵌容器、自动装配、生态完整,适合快速开发Web应用并快速部署,而不是只会说“比较流行”。
“逻辑删除是怎么做的?”从deleted字段、MyBatis Plus配置、到查询时自动过滤,再到和唯一索引的冲突处理,这个完整链路讲清楚,基本就是满分回答。
“数据权限怎么控制的?”从自定义注解、AOP拦截、到用户辖区范围的拼接条件,这个设计怎么做的就怎么说。
“如果有1万老人,系统会不会卡?”不用慌,可以从分页查询、索引优化、批量查询减少N+1几个角度讲,虽然不深入,但足以证明你思考过。
“系统还有什么可以改进的地方?”这个问题看起来很凶,其实是给你送分。认认真真说两个真实存在的不足就行,比如现在上传文件存在本地磁盘,生产环境更适合对象存储;统计功能目前做得比较基础,后续可以做更深入的预警分析。切忌说“没有不足”,那在评委眼里就是自大。
7.3 如果让我重来一次
做完整个项目再回头看,最想改的一点是,我应该更早开始写测试用例,而不是把测试拖到最后面补。另一个是统计模块的设计可以更早定清楚,因为我一开始只用简单SQL统计,后面要实现图表时发现缺了很多中间字段,不得不临时在后端做了大量聚合代码。
如果你正在做类似的系统,我的建议很简单:前期把数据库设计熬扎实,中期把权限和数据隔离认真做一遍,后期一定要准备一份能串起整个业务闭环的演示数据。做到这三点,这个毕业设计就不会是那种“看起来能用,实际经不起问”的纸上项目。至于调试过程中那些让人崩溃的日期格式、上传大小、逻辑删除冲突,只要你提前看到这篇文章,就都不会再成为你的拦路虎。