2025年用SpringBoot+Vue搭了一套社区老人健康管理系统,把源码级经验全说透
先说结论:这套系统不是那种“看起来高大上、跑起来一堆坑”的教学Demo,而是一个真正能落到社区场景里的前后端分离项目。技术栈就是标题里那四件套——SpringBoot、Vue、MyBatis、MySQL,后端提供接口,前端做页面交互,MyBatis负责数据库操作,MySQL存储所有健康档案和体检数据。我把它完整跑通之后,最大的感受是:老人健康管理的核心难点根本不在界面多好看,而在数据怎么组织、异常怎么发现、提醒怎么触达。 这套系统在这三件事上给了很务实的解法,适合正在学JavaWeb开发的学生、准备做毕业设计的本科生,以及社区医疗信息化相关的小型项目开发者参考。
1. 这个项目到底在做什么:场景拆解与核心模块
1.1 社区场景下老人健康管理真正缺的是什么
我一开始以为这种系统就是做一个“老人信息增删改查”的CRUD,真正动手梳理需求才发现没那么简单。社区健康管理的真实痛点有三个:一是老人分散在各网格,纸质档案没法统一查;二是体检数据来自不同批次,时间跨度大,异常指标没人盯;三是家属和网格员之间信息断层,老人血压突然高了、药该吃了,这些事往往没人第一时间知道。
这套系统的价值就是把这三件事数字化:给每个老人建一份可追踪的健康档案,把历次体检数据按时间线存起来,再通过规则引擎自动识别异常指标并生成提醒记录。落到技术层面,核心就是实体建模和状态流转——老人、档案、体检记录、提醒记录这几个实体,加上“新增档案→录入体检→触发预警→生成提醒→处理反馈”这条业务链条。
1.2 功能模块怎么划分才不虚胖
项目拆成了六个核心功能模块,我按实际开发顺序整理了一下:
| 模块 | 核心功能 | 涉及数据库表 | 前端页面 |
|---|---|---|---|
| 老人档案管理 | 老人基本信息的增删改查、按社区/网格筛选 | elderly_info | 档案列表、档案编辑弹窗 |
| 健康档案管理 | 既往病史、过敏史、家族病史等医疗信息 | health_record | 健康档案详情页 |
| 体检数据管理 | 历次体检数据录入、指标趋势查询 | physical_exam | 体检记录列表、指标表单 |
| 异常预警管理 | 异常指标自动识别、预警记录生成与处理 | alert_record | 预警列表、处理状态流转 |
| 用药提醒管理 | 用药计划维护、按时提醒记录 | medication_plan | 用药计划页、提醒看板 |
| 系统管理 | 用户登录、角色权限、操作日志 | sys_user, sys_role | 登录页、用户管理页 |
这样划分的好处是每个模块边界清晰,前后端联调时接口路径和页面路由能一一对应,不会出现“一个页面里揉了三块业务”的情况。
1.3 数据模型设计的第一版思路
数据库建模阶段我看了很多同类项目的表设计,大多数把老人信息和健康记录塞在一张表里,这样查询虽然简单,但后续扩展会很难受。我采用了“一体两翼”的模型:中间是老人基本信息表,左边挂健康档案表(一对一),右边挂体检记录表(一对多),再往外派生预警记录和用药计划。
这一版建模的核心逻辑是:体检数据必须按“一次体检一条记录”的方式存储,每条记录里再存各项指标值,这样既支持历史趋势查询,也为后续接入物联网设备测量数据留了余地。具体的SQL建表语句我会在第五节展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型不是拍脑袋:这套组合的底层逻辑
2.1 SpringBoot在2025年依然是中小型管理系统的最优解
为什么不用Spring Cloud那套微服务?社区健康管理系统这个体量,单机部署、十几个接口、日均几百次请求,上微服务纯属给自己加戏。SpringBoot的价值在于零XML配置、内嵌Tomcat、起步依赖一把梭,一个spring-boot-starter-web就能把Controller层跑起来。2025年SpringBoot 3.x已经相当成熟,但我建议新手选2.7.x版本,原因后面讲版本坑时细说。
再说了,SpringBoot的自动配置机制太适合这种业务管理系统了:数据源配好、MyBatis的Mapper扫描配好、事务管理器自动生效,剩下的事情就是专心写业务逻辑。
2.2 Vue和MyBatis各自解决了什么问题
Vue这边,我用的是Vue 2 + Element UI的组合。有人会问为什么不上Vue 3,答案是这套组合的生态最稳:Element UI的表格、表单、弹窗组件直接可用,后台管理系统需要的90%的界面组件它都有,不用自己造轮子。Vue的数据双向绑定让表单处理特别顺——体检数据录入页里,血压、血糖、心率这些字段绑定到data对象,提交时直接拿到整个对象传给后端,省掉一堆DOM操作代码。
MyBatis这里就要聊点实在的了。它有两点是JPA和MyBatis Plus替代不了的:一是SQL由开发者完全掌控,复杂查询多表关联时能精确控制执行计划;二是动态SQL能力极强,比如老人列表按姓名、社区、年龄段组合筛选,用<where>加<if>标签拼接条件非常灵活。热词里出现的“mybatis动态sql”大概率就是指这个,我项目里也用了很多。
2.3 为什么故意不选MyBatis Plus
热词搜索里“mybatis plus批量插入”“mybatis plus逻辑删除”被频繁搜索,说明很多人在用MP。但我这个项目是刻意选的纯MyBatis,原因有两点:第一,学习价值不同。纯MyBatis需要手写ResultMap和SQL,能逼你把数据库字段和Java属性的映射关系彻底搞明白,换成MP后这些细节会被封装掉。第二,控制粒度不同。社区健康系统的查询条件经常是“年龄大于70且最近一次体检收缩压高于160”,这种带业务逻辑的统计SQL,自己写远比自己拼接Wrapper方便。
提示:如果你只是做毕业设计赶时间,用MP确实能省不少代码。但如果系统里需要复杂的报表统计SQL,还是建议手写XML,日后的维护成本会低很多。
3. 搭骨架阶段最容易忽略的几件事
3.1 初始化SpringBoot项目时的版本对齐问题
这是我第一个想骂人的坑。2025年Spring Initializr默认生成的是SpringBoot 3.x,要求JDK 17起步。但很多人电脑上装的是JDK 8,SpringBoot 2.7.x才是兼容JDK 8的最后一个主线版本。所以我的建议是:新建项目时直接选SpringBoot 2.7.18 + JDK 8 + MyBatis 2.3.x + MySQL 5.7/8.0,这一套组合经过大量项目验证,坑最少。
在pom.xml里,核心的依赖配置长这样:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
热词里特别扎眼的“springboot版本太高”就是这个原因——很多教程写于2021年,你按教程配结果下到了3.x的依赖,JDK不兼容直接起不来。
3.2 MyBatis核心配置:驼峰映射与Mapper扫描
MyBatis配置里最容易忽略的是驼峰命名映射。数据库字段习惯用下划线(elderly_name),Java属性习惯用小驼峰(elderlyName),如果不打开驼峰映射,查询出来的对象里全是null。在application.yml里的配置:
yaml复制mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.health.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true这一行值五分钱,但忘记它的代价是查出来的数据全是null,排查半天还以为是SQL写错了。另外log-impl建议打开,开发阶段能在控制台直接看到MyBatis执行的SQL语句和参数,对调试动态SQL非常有帮助。
3.3 Vue项目搭建和代理转发
前端这边,我用vue-cli创建项目,选Router和Vuex。开发环境的跨域问题必须处理:后端接口跑在8080,前端跑在8081,直接请求肯定是跨域。最优雅的方案是在vue.config.js里配置devServer代理:
javascript复制module.exports = {
devServer: {
port: 8081,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
}
这样前端请求/api/elderly/list的时候,实际转发到后端的/elderly/list,规避了开发环境的跨域问题。记得后端Controller里不要加@CrossOrigin,否则两个跨域方案会冲突,反而出奇怪的问题。
4. 核心功能模块代码实现:手写一遍才是真学到
4.1 老人档案模块:从Mapper到Controller的完整链路
老人档案模块是典型的基础数据维护功能,但很多细节值得展开。先看实体类:
java复制@Data
@TableName("elderly_info")
public class ElderlyInfo {
private Integer id;
private String elderlyNo; // 老人编号
private String name; // 姓名
private Integer age; // 年龄
private String gender; // 性别
private String phone; // 联系电话
private String address; // 住址
private String communityName; // 所属社区
private String emergencyContact; // 紧急联系人
private String emergencyPhone; // 紧急联系电话
private Integer status; // 1-正常 0-已注销
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Mapper接口定义分页查询方法,因为前端用的是Element UI的分页组件,所以后端要支持分页参数:
java复制public interface ElderlyInfoMapper {
List<ElderlyInfo> selectPageList(@Param("name") String name,
@Param("communityName") String communityName,
@Param("offset") Integer offset,
@Param("limit") Integer limit);
Long countPage(@Param("name") String name,
@Param("communityName") String communityName);
}
对应的XML文件里用动态SQL处理组合条件:
xml复制<select id="selectPageList" resultType="com.example.health.entity.ElderlyInfo">
SELECT * FROM elderly_info
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="communityName != null and communityName != ''">
AND community_name = #{communityName}
</if>
AND status = 1
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
这里用<where>标签的好处是:当所有条件都为空时,自动去掉WHERE关键字;当前面有条件而后面追加时,自动去掉多余的AND。手写一次这个动态SQL,你就明白为什么MyBatis在Java持久层框架里地位这么稳了。
4.2 体检数据模块:批量插入与参数传递
体检数据模块有个特殊场景:老人做一次全面体检,会生成几十项指标。如果一条条插入,数据库要执行几十次INSERT,性能很差。我在项目里用了MyBatis的批量插入功能,在Mapper接口里一次性接收一个List:
java复制int batchInsert(@Param("list") List<PhysicalExam> examList);
XML里的写法:
xml复制<insert id="batchInsert" parameterType="list">
INSERT INTO physical_exam (elderly_id, exam_date, item_name, item_value, unit, ref_range, is_abnormal)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.elderlyId}, #{item.examDate}, #{item.itemName}, #{item.itemValue},
#{item.unit}, #{item.refRange}, #{item.isAbnormal})
</foreach>
</insert>
<foreach>标签在这里起的作用是把List展开成一条多VALUES的INSERT语句,一次网络往返就能插入几十条数据。热词里有“mybatis plus批量插入”的搜索,说明很多人被这个问题卡过,纯MyBatis的<foreach>写法其实更直观。
4.3 异常预警模块:规则引擎的设计与实现
这是整个系统技术含量最高的模块。预警规则本质上是一组“如果指标值超过阈值,则生成预警记录”的条件判断。我设计了一张预警规则表,把阈值配置化而不是写死在代码里:
sql复制CREATE TABLE alert_rule (
id INT PRIMARY KEY AUTO_INCREMENT,
rule_name VARCHAR(100) NOT NULL COMMENT '规则名称',
item_code VARCHAR(50) NOT NULL COMMENT '指标编码',
condition_type VARCHAR(20) NOT NULL COMMENT '条件类型:GT大于, LT小于, BETWEEN区间',
threshold_min DECIMAL(10,2),
threshold_max DECIMAL(10,2),
alert_level VARCHAR(10) COMMENT '预警等级:high/mid/low',
is_active TINYINT DEFAULT 1
);
业务层的判断逻辑用策略模式实现——不同指标类型可能有不同的判断策略,但当前版本只需要统一的区间判断就够了:
java复制public AlertRecord checkAbnormal(PhysicalExam exam) {
AlertRule rule = alertRuleMapper.selectByItemCode(exam.getItemName());
if (rule == null) {
return null;
}
boolean isAbnormal = false;
double value = Double.parseDouble(exam.getItemValue());
switch (rule.getConditionType()) {
case "GT":
isAbnormal = value > rule.getThresholdMax();
break;
case "LT":
isAbnormal = value < rule.getThresholdMin();
break;
case "BETWEEN":
isAbnormal = value < rule.getThresholdMin()
|| value > rule.getThresholdMax();
break;
}
if (isAbnormal) {
AlertRecord alert = new AlertRecord();
alert.setElderlyId(exam.getElderlyId());
alert.setItemName(exam.getItemName());
alert.setItemValue(exam.getItemValue());
alert.setAlertLevel(rule.getAlertLevel());
alert.setStatus(0); // 0-待处理 1-已处理
return alert;
}
return null;
}
这套设计的巧妙之处在于:以后新增预警规则不需要改代码,只要往alert_rule表里插一条数据就行。社区医生可以自己在后台配置“收缩压超过160就预警”,而不是等开发人员改代码。
4.4 用药提醒模块:定时任务与通知
用药提醒是我个人觉得最贴心的模块。它做的事是:根据老人每天的用药计划(几点吃、吃几片),在指定时间生成一条提醒记录。实现上用的是Spring的@Scheduled定时任务,每30分钟扫描一次当前时间需要提醒的用药计划:
java复制@Scheduled(cron = "0 */30 * * * ?")
public void scanMedicationPlan() {
List<MedicationPlan> plans = medicationPlanMapper.selectNeedRemind(LocalTime.now());
for (MedicationPlan plan : plans) {
MedicationRemind remind = new MedicationRemind();
remind.setPlanId(plan.getId());
remind.setElderlyId(plan.getElderlyId());
remind.setRemindTime(LocalDateTime.now());
remind.setStatus(0); // 0-未确认 1-已确认
medicationRemindMapper.insert(remind);
}
}
前端页面上显示当天所有未确认的提醒,网格员可以手动标记“已确认服药”。这个模块做成之后,社区老人漏服药的情况明显减少——这正是健康管理系统应该有的样子,技术在背后默默工作,人在正确的位置做决策。
5. 数据库设计:字段类型与索引选择的经验教训
5.1 体检指标表怎么设计才不踩坑
体检指标是这套系统最特殊的表。每个人做一次体检出几十项指标,如果把每项指标都设计成独立的列,那体检项目一变化就要改表结构,绝对不可取。我用了“一行一项指标”的纵表设计:
sql复制CREATE TABLE physical_exam (
id INT PRIMARY KEY AUTO_INCREMENT,
elderly_id INT NOT NULL COMMENT '老人ID',
exam_date DATE NOT NULL COMMENT '体检日期',
item_name VARCHAR(50) NOT NULL COMMENT '指标名称',
item_value VARCHAR(50) NOT NULL COMMENT '指标数值',
unit VARCHAR(20) COMMENT '单位',
ref_range VARCHAR(50) COMMENT '参考范围',
is_abnormal TINYINT DEFAULT 0 COMMENT '是否异常',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_elderly_exam_date (elderly_id, exam_date)
) COMMENT '体检指标明细表';
注意这里item_value用的是VARCHAR而不是DECIMAL,因为体检指标五花八门,有的数值是“阴性/阳性”,有的是“+++”,用数值类型会直接笑场。字段类型一定要根据业务实际取值来决定,不要一刀切用DECIMAL。热词里那个“mysql中int+5”,说的就是类型不匹配造成的隐式转换问题——这类问题在指标值这种混搭数据场景里特别容易踩。
5.2 逻辑删除还是物理删除:我的选择与理由
系统里的老人档案涉及真实健康数据,哪怕老人离世或者搬走了,历史体检记录也有保留价值。所以我选择了逻辑删除:在elderly_info表上加status字段,1-正常,0-已注销。删除操作实际是执行UPDATE elderly_info SET status = 0 WHERE id = ?,而不是DELETE。
所有查询语句默认带上AND status = 1(就像4.1节的XML里写的),这样数据不会丢,但业务上又表现出了删除的效果。热词里“mybatis plus怎么将逻辑删除的数据也查询出来”说明很多人被这个机制坑过——如果你需要恢复数据或做数据统计,别忘了要查所有状态的数据。
5.3 索引设计的两个必须和三个禁忌
社区老人系统的数据量不会太大(几万条级别),但索引设计还是要认真做。我的经验是:
- 必须给
elderly_info表的community_name建索引,因为网格员查询时最常用的筛选条件就是这个 - 必须给
physical_exam表建(elderly_id, exam_date)联合索引,因为“查某个老人最近几次体检”是最常见的查询模式 - 禁忌一:不要在VARCHAR字段上随便加索引,比如老人的
address字段,选择性低还占空间 - 禁忌二:不要对
item_value这种字段建索引,它的值五花八门,索引效率极低 - 禁忌三:不要一张表建十几个索引,数据量不大的表索引太多反而拖慢写入速度
6. 前后端联调时的典型报错与排查链路(附完整思路)
6.1 “Eclipse里SpringBoot集成MyBatis一直报错downloading”怎么破
热词里有条特别扎眼的搜索:“eclipse里springboot集成mybatis一直报错。downloading…”。我太懂这个场景了,新手拿着老教程配Eclipse + SpringBoot + MyBatis,卡在Maven依赖下载这一步:无限downloading,或者下载到一半卡死。
这个问题八成是Maven仓库源的问题。国内网络访问Maven中央仓库极不稳定,需要在settings.xml里换阿里云镜像:
xml复制<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
换完之后,Eclipse里右键项目,Maven → Update Project,勾选Force Update of Snapshots/Releases,强制刷新依赖。这一步能解决大部分“downloading卡死”的问题。如果还不行,检查一下本机~/.m2/repository目录,把未下载完整的*.lastUpdated文件删掉再重新拉。
6.2 MySQL连接失败的排查顺序
项目里会遇到的另一个高频报错是Access denied for user 'root'@'localhost'或者Public Key Retrieval is not allowed。前者是密码错了或者权限没开,后者是MySQL 8.0的caching_sha2_password认证插件问题。
我的排查顺序是:第一步检查MySQL服务是否启动,Windows下直接看任务管理器里有没有mysqld.exe进程;第二步确认账号密码,命令行用root登录测试;第三步看连接URL的配置,MySQL 8.0的URL要加上allowPublicKeyRetrieval=true&useSSL=false:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/community_health?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
那个serverTimezone=Asia/Shanghai也特别重要,不设置的话数据库连接会报时间时区相关的SQLException,中文环境下这个报错信息还特别隐晦,新手根本看不懂。
6.3 插入数据不报错但数据库里没有:元凶是事务没提交
热词里有一条“mybatis plus insert 数据没有写成功,但是也没有报错”的搜索,我项目里也遇到过类似情况。排查链路是这样的:SpringBoot默认的事务管理器的回滚条件是RuntimeException,而MyBatis的SqlSession在Spring管理下会自动提交事务,但前提是方法必须经过Spring代理。
我踩过的一个真实场景是:在一个工具类里直接@Autowired注入Mapper,然后在一个没有@Transactional的方法里调用insert方法。数据确实插进去了,但由于MyBatis的SqlSession没有绑定到Spring事务上,在SqlSession关闭时(默认不自动提交)数据就被回滚了。整个操作没有任何异常,看起来就是“没报错但没写进去”。
解决方案很简单:把业务方法加上@Transactional,或者确认调用链是从Controller → Service(带@Service注解) → Mapper的完整Spring代理链路。
6.4 Vue前端常见问题:路由跳转和m3u8播放
前端的热词里居然有“vue播放m3u8”,这个其实和本系统无关,但说明Vue开发者确实会在视频播放场景踩坑。在我的健康系统里,没有视频需求,但有一个体检报告附件上传后预览的功能。如果后面你想在Vue里播放体检相关的视频指导,可以直接用video.js加videojs-contrib-hls插件,这个组合能处理m3u8格式的流媒体文件。前端路由那块,注意用this.$router.push而不是window.location.href跳转,避免页面整个刷新丢失Vuex里的登录状态。
7. 项目跑通之后还能怎么延伸:几个高性价比的进阶方向
7.1 体检趋势图:ECharts画血压血糖变化曲线
系统上线后,社区医生提了第一个需求:能不能把某位老人近半年的血压值画成折线图,直观看到变化趋势?这个需求用ECharts很合适,前端在elderly_detail.vue页面里嵌入一个折线图组件,数据从后端新接口/api/exam/trend/{elderlyId}拿。后端写一个按日期分组的SQL,把收缩压和舒张压的历次数值查出来返回给前端。这个功能的业务价值极高——趋势比单次数值更能说明问题,而且能提前预警心脑血管疾病风险。
7.2 消息推送:不主动打扰但要触达关键人
现在的提醒只能登录系统才能看到,如果网格员没登录电脑就错过了。我建议后续加一个简单的推送通道:预警记录生成时,通过腾讯地图或微信小程序的服务通知推送给家属。热词里“用在vue里的腾讯地图”涉及类似集成,说明很多开发者都在Vue项目里做过第三方服务对接。推送通道的关键是设计好消息模板和接收人关系表——一个老人可以绑定多个家属,一个家属可以绑定多个老人。
7.3 权限系统升级:从单管理员到三角色
当前版本的系统管理比较简单,只有管理员和网格员两个角色。后续可以扩展成超级管理员、社区医生、网格员、家属四个角色,每个角色的数据权限不同:医生看全部健康数据但只能读,网格员管日常提醒和档案录入,家属只看自己绑定的老人数据。用Spring Security + JWT实现基于角色的访问控制,登录接口返回token,前端每次请求带在Authorization头里。这套升级是技术含金量最高的,也是面试时最能讲的亮点。
8. 关于部署和上线:从本地到服务器的最后一公里
项目开发完成后,部署又是一个新的关口。我用的方案是:后端打jar包,用Docker部署;前端npm run build生成静态文件,用Nginx托管。
后端Dockerfile很简单:
dockerfile复制FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/community-health-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
构建和启动命令:
bash复制mvn clean package -DskipTests
docker build -t community-health .
docker run -d -p 8080:8080 --name health-app community-health
前端构建后,把dist目录下的文件复制到服务器的Nginx站点目录,配置好/api的反向代理指向8080端口。热词里“springboot jdk1.8打包到docker desktop”说明很多人在本地用Docker Desktop验证镜像,这个流程没问题——但注意Docker Desktop和本机MySQL的通信要走host.docker.internal这个特殊域名,而不是localhost,否则容器里的Java进程连不上本机的数据库。
整个项目从设计、开发到部署,我最大的体会是:技术选型决定开发效率的上限,数据模型决定业务扩展的下限。 SpringBoot + Vue + MyBatis + MySQL这套组合,虽然被说“老套”,但它稳定、生态全、资料多,对于一个社区级的健康管理系统来说就是最务实的选择。如果你也被这个题目吸引了,我的建议是别光看源码,自己动手把每个模块写一遍,遇到报错就按我上面说的排查思路去定位——踩过一轮坑之后,你对这几个框架的理解会上一个台阶。
