
做医院后台管理系统这个项目说句实话是从一个所有 Java 开发者都会遇到的“毕设/课程设计”需求里长出来的基于SpringBootVue的医院后台管理系统后端用 JavaMySQLMyBatis前端用 Vue工程结构干净业务闭环完整源码能直接跑起来把业务串通。这类项目市面上很多但真正能让人从登录到挂号、从门诊到药房走一遍完整流程的并不算多。这篇文章不打算只贴一堆截图和模块列表而是想把我实际做这个系统时从数据库设计、后端分层、前端路由到前后端联调踩过的坑按实操顺序完整梳理一遍。不管你是准备拿来当毕设、课设还是想通过一个完整项目把 SpringBoot 和 Vue 的协作逻辑吃透这套东西应该都能帮上忙。1. 项目全貌拆解医院后台管理系统到底在做什么1.1 医院业务场景中的核心痛点医院后台管理系统本质上是把线下门诊流程搬进信息化系统。我最早接触这个需求时以为就是做个简单的 CRUD 增删改查真正去梳理医院业务流程才发现事情没那么简单。一个患者走进医院从挂号开始到最终取药离院中间至少要经过挂号员、医生、收费员、药房药师四类角色每个角色之间的数据必须闭环衔接。挂号的医生要能被医生接诊看到医生开的处方要能流转到收费和药房收费完成的信息要能反馈回发药窗口。如果只做几个独立的增删改查页面系统根本没法在真实场景里用起来。所以我把整个系统的核心业务流程先画出来后续所有模块都围绕这条主线展开患者到挂号台 → 挂号员登记信息老患者直接按身份证调取档案→ 选择科室和医生 → 生成挂号记录 → 医生在诊室看到候诊列表 → 接诊后写门诊病历、开处方 → 患者去收费处缴费 → 收费记录更新 → 药房看到已缴费处方后发药 → 库存扣减 → 每个环节的数据都回写对应表中。这条主线决定了数据库至少要有关联的用户、患者、医生、科室、挂号、病历、处方、药品、收费等核心表也决定了后端接口不能只做孤立的增删改查而是要围绕“状态流转”设计。1.2 系统模块与角色权限的划分方式模块划分上我没有按照教科书式的“系统管理、业务管理、统计报表”三大块硬套而是顺着业务流程拆出了六个核心模块系统管理、科室医生管理、患者管理、门诊业务挂号、接诊、处方、药品药房管理、收费与统计。每个模块再往下细分比如门诊业务里包含挂号登记、候诊列队、病历书写、处方开立。这种拆法的一个好处是业务边界非常清晰后端 Controller 和前端页面能一一对应开发的时候不需要反复跨模块改代码。角色权限我设置了四种系统管理员负责用户、科室、药品的基础数据维护和所有模块的访问权限挂号收费员主要操作挂号登记、收费和退号医生只看到自己的接诊列表、病历书写和处方开立药房人员处理药品库存、出入库和发药确认。前端通过路由守卫控制菜单显示后端通过拦截器校验接口权限两端的权限逻辑必须保持一致否则就会出现前端隐藏了按钮、后端接口却照样能调通这种尴尬情况。1.3 技术选型背后的几点考量技术栈选型上SpringBoot Vue MySQL MyBatis 这套组合在今天依然是很稳健的选择原因有三点。第一SpringBoot 让后端搭建成本变得极低。项目启动、内置 Tomcat、自动配置、starter 依赖管理我只需要专注写业务代码。相比传统 SSM 框架需要手动拼一堆 XML 配置SpringBoot 的项目骨架十分钟内就能跑起来。第二MyBatis 在这种业务场景里比 JPA 更顺手。医院系统的查询条件非常多变比如挂号记录列表要支持按日期、按科室、按患者姓名、按状态组合筛选药房库存要按药品名模糊搜索和按有效期区间过滤。用 MyBatis 的动态 SQL一个if标签就能灵活拼接查询条件SQL 完全可控。JPA 的自动派生的查询方法在这种复杂查询场景下反而绕来绕去。第三Vue Element UI或 Element Plus的前后端分离模式开发调试效率确实高。前端用 Axios 请求后端接口后端只负责返回 JSON两边可以并行开发。开发阶段用 Vite 或 Webpack 的代理解决跨域生产阶段把前端打包后的静态文件丢进 SpringBoot 的静态资源目录用同一个端口对外提供服务整个部署链路非常简洁。2. 数据库设计与后端核心实现2.1 核心业务表怎么设计才不返工数据库设计是整个项目的地基这块如果没想清楚后面写代码会被迫不停改表结构。我按业务模块逐块设计先规划每张表要承载什么数据再确定关联方式。核心表大致如下用户表 sys_user字段包括用户ID、登录名、密码、真实姓名、手机号、角色类型、所属科室ID、启用状态、创建时间。密码必须加密存储明文密码在任何正规项目里都是致命的。科室表 department科室ID、科室名称、科室位置、科室介绍、排序号。医生表 doctor医生ID、所属科室ID、医生姓名、职称、擅长领域、排班信息、是否出诊。这里要注意医生和用户的关系一个医生账号对应一个用户账号通过用户ID关联这样医生既能登录系统又能在医生表中维护自身的专业信息。患者表 patient患者ID、姓名、性别、年龄、身份证号、手机号、家庭住址、过敏史、既往病史、建档时间。身份证号是最常用的检索条件需要加索引。挂号表 registration挂号ID、患者ID、科室ID、医生ID、挂号日期、挂号时段、挂号费、状态1-已挂号 / 2-已就诊 / 3-已退号 / 4-已作废、操作员ID、创建时间。状态字段是整个门诊流程流转的核心。门诊病历表 medical_record病历ID、患者ID、医生ID、主诉、现病史、初步诊断、处理意见、复诊提醒、创建时间。处方表 prescription 和处方明细表 prescription_item主表记录患者、医生、开方时间、总金额明细表记录每种药品、数量、用法用量、单价、小计。为什么要拆两张表因为一张处方有多条药品明细如果全塞在一张表里要么产生大量冗余重复的处方主信息要么只能存一个逗号拼接的药品字符串后续统计和发药都对不上。药品表 drug药品ID、药品编码、名称、规格、生产厂家、单位、单价、库存数量、库存上限、有效期、是否处方药。收费表 charge收费ID、患者ID、关联业务单号挂号单或处方单、收费项目类型、收费金额、收费时间、收费员ID、支付方式、状态。病床表和住院表病床ID、所属科室、床号、状态空闲/占用住院表关联患者、床号、入院时间、出院时间。表与表之间的关系我用了最直接的外键逻辑但不在数据库里强制物理外键而是通过业务代码保证数据一致性。理由很简单物理外键在 MySQL 高并发场景下会带来额外的锁开销而且后期业务调整时需要删表重建很麻烦。实际开发中我一般用逻辑外键也就是在子表中加父表的 ID 字段通过关联查询维护。如果做毕设需要体现“完整性”可以在设计文档里把 E-R 图画出来再说明物理外键与逻辑外键的取舍。2.2 后端分层结构与关键配置后端我用标准的四层结构Controller → Service → Mapper → Entity再配合一个统一的返回体 Result 和全局异常处理器。Entity 里的字段对应数据库表的列Mapper 层负责写 SQLService 层处理业务逻辑Controller 层只做参数接收和结果返回。SpringBoot 的配置文件里几个关键配置值得单独提一下。数据库连接串要特别注意时区问题我用的是spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useSSLfalseserverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8allowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case 这个配置非常重要。数据库字段是 patient_nameJava 实体是 patientName没有这个配置的话MyBatis 查询结果映射就会全部为 null而且你还不容易察觉。log-impl 配置为 StdOutImpl控制台会打印完整 SQL 和参数排查问题时能省不少时间。MyBatis 的 Mapper 层我采用的是 XML 和注解混用的方式。简单的单表增删改查直接用注解比如Select、Insert复杂动态查询比如挂号记录的多条件组合查询则写在 XML 里用where和if拼接条件。分页我用的 PageHelper在 Service 层直接PageHelper.startPage(pageNum, pageSize)紧接着的下一句 Mapper 查询会被自动加上 LIMIT非常方便。不过要注意 PageHelper 只对它后面的第一条查询生效如果中间插入了其他操作分页就会失效这个坑我在项目初期踩过。2.3 MyBatis 缓存机制里容易被忽略的坑说到 MyBatis很多人面试和实际开发都喜欢聊缓存但它也是一把双刃剑。MyBatis 一级缓存是 SqlSession 级别的同一个 SqlSession 执行相同的 SQL 会直接从缓存取默认开启。问题在于 SpringBoot 整合 MyBatis 后每个 Mapper 方法调用通常都会创建和销毁独立的 SqlSession所以一级缓存的意义其实很有限。二级缓存是 namespace 级别的跨 SqlSession 共享默认关闭。如果业务对数据实时性要求高比如药房库存开了二级缓存后一个线程更新库存另一个线程查询旧缓存很容易出现发药记录和库存对不上的脏读。我的建议是医院后台管理系统里涉及库存、挂号状态的查询一律不要开二级缓存宁可每次查数据库也不要去背数据不一致的风险。另外有个小技巧使用 MyBatis 查询时如果返回的结果是Map而不是实体对象需要注意map-underscore-to-camel-case对 Map 类型是不生效的返回的 key 依然是下划线风格前端拿到字段名会不一致。对于灵活的统计报表接口我更推荐建一个专门的 VO 类来承接查询结果字段名显式指定避免踩这个坑。3. 前端 Vue 项目搭建与页面实现3.1 工程结构、路由设计与权限控制前端项目我建议直接用 Vue3 Vite Element Plus如果对 Vue2 更熟用 Vue2 Webpack Element UI 也能走通思路是一样的。工程结构大致分为几个目录views 放页面组件router 放路由配置storePinia 或 Vuex放用户状态api 放 Axios 请求封装utils 放工具函数。每个模块的页面放到对应子目录比如门诊相关的页面都放 views/outpatient 下方便统一管理。路由设计上我采用了动态路由的思路。用户登录后后端根据角色返回可访问的菜单列表和路由名称前端通过router.addRoute动态注册同时配合全局前置守卫做登录校验。守卫的核心逻辑是router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })这个写法是最基础也最好用的保证未登录用户访问任何页面都会被弹回登录页。菜单部分根据后端返回的权限数据用v-for渲染侧边栏不同角色登录后看到的菜单天然不同。这套方案的好处是权限控制集中在后端前端只是被动展示后端改了权限前端不用重新发布。3.2 几个核心页面的实现思路登录页看着简单但要处理好几个细节表单校验、记住密码、登录状态持久化。用户输入账号密码后调用/api/login接口后端校验通过返回 JWT token 和用户基本信息前端把 token 存到 localStorage然后在 Axios 请求拦截器里统一加上 Authorization 头这样后续所有接口请求都会带上身份凭证。挂号登记页是挂号收费员每天使用频率最高的页面。我先做一个患者信息区输入身份证号后自动查询是否存在患者档案存在就直接带出姓名、性别和联系方式不存在则进入新建患者模式这样新老患者都能快速完成挂号。然后选择科室和医生科室用下拉选择医生列表根据所选科室动态加载提交时同时生成挂号记录并计算挂号费。页面上还要把“最近挂号记录”列出来方便挂号员退号或查询。医生接诊页的逻辑核心是候诊列表和病历联动。候诊列表显示当前登录医生名下的挂号记录状态为“已挂号”的才能接诊。点击接诊后右侧弹出病历表单自动带出患者 ID 和基本信息医生填写主诉、诊断和处理意见病历保存成功后把挂号状态更新为“已就诊”。整个页面的难点在于状态切换要及时刷新我采用的方式是接诊成功后直接更新当前列表项的状态而不是重新拉取接口这样交互手感会流畅很多。药房发药页相对简单默认展示已缴费未发药的处方列表药师点击处方查看明细核对无误后点击“发药”同时后台执行两张表的操作更新处方明细的状态为“已发药”扣减药品表库存。这里要注意发药和减库存必须在同一个事务里否则会出现处方显示已发药、库存却没扣的严重问题。数据统计页面我用了 ECharts 图表。后端聚合接口返回按日期的挂号量趋势、各科室门诊量占比、收费金额统计前端用饼图和折线图展示。实现细节是图表在页面加载时调用接口获取数据页面 resize 时调用chart.resize()防止图表变形这些代码属于常规工作量。3.3 Axios 封装、跨域处理与打包注意事项Axios 一定要封装不封装的后果是每个组件里都要重复写请求地址、超时时间、错误处理代码冗余且难以维护。我在 utils 里建了一个 request.js统一做的事包括设置基础 URL设置请求拦截器自动附加 token设置响应拦截器统一处理 HTTP 错误码和业务错误码遇到 401 自动跳转登录页。开发环境的跨域处理有两种方案。第一种是后端加 CORS 配置允许前端开发服务器地址跨域访问第二种是走前端代理配置 Vite 的server.proxy或 Webpack 的devServer.proxy把/api开头的请求转发到http://localhost:8080。这两种我都试过推荐用前端代理方案因为生产环境不需要额外改后端接口的跨域配置安全性也更好。后端只需要保留一个兜底的 CORS 配置防止某些场景漏配。前端开发完成后的打包环节最常被问到的就是“怎么把 Vue 项目打包放进 SpringBoot 里”。步骤其实很清楚先npm run build生成 dist 目录然后把 dist 里的文件复制到 SpringBoot 的 src/main/resources/static 目录下重新启动后端浏览器访问http://localhost:8080就是前端首页。这里有一个关键问题Vue Router 如果用了 history 模式打包后直接访问非首页路径比如直接刷新 /register会报 404因为后端没有对应的路由转发规则。解决方式要么路由改成 hash 模式路径上带 #要么在 SpringBoot 里写一个转发 Controller把非/api开头的请求统统转发到 index.html。我实际项目中两种都试过更推荐 hash 模式省事且不会有线上资源路径问题如果你更在意 URL 美观那就在后端加一个这样的小配置类Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{path:[^\\.]*}).setViewName(forward:/index.html); } }这个配置会把所有不带点后缀的路径转发到 index.html让 Vue Router 接管路径解析。4. 完整运行实操记录从 0 到 1 把项目跑起来4.1 后端 SpringBoot 项目初始化我用的是 IDEA新建 SpringBoot 项目时通过 Spring Initializr 选择 Java 8 或 Java 11、SpringBoot 2.7.x 版本。这里补充一句SpringBoot 3.x 虽然已经发布很久但很多第三方依赖还停留在 javax 包名时代如果你不熟悉 jakarta 迁移建议用 2.7.x 版本起步兼容性最好。依赖引入包括 spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java或 com.mysql:mysql-connector-j、druid-spring-boot-starter连接池、lombok、spring-boot-starter-validation。分页插件单独引入com.github.pagehelper:pagehelper-spring-boot-starter。项目建好后我先把包结构建好controller、service、mapper、entity、vo、common、config、utils。common 下面放统一的返回体 Result 类和全局异常处理器。全局异常处理器非常有必要它能把所有业务异常、校验异常、SQL 异常统一转换为固定格式的 JSON 返回避免前端拿到的错误信息五花八门。数据库方面用 Navicat 或命令行创建 hospital_db 数据库执行建表 SQL。我建议后端实体类配合数据库表一起写先建库建表再在代码里创建实体类这样字段不会错位。建表时要注意字符集用 utf8mb4不然存 emoji 或生僻字会报错。4.2 核心接口实现与事务控制后端接口里比较有代表性的是“发药减库存”这个接口它体现出事务控制的重要性。Service 层的代码如下Transactional(rollbackFor Exception.class) public void dispenseDrug(Long prescriptionId) { // 1. 校验处方状态 Prescription prescription prescriptionMapper.selectById(prescriptionId); if (prescription null || !已缴费.equals(prescription.getStatus())) { throw new BusinessException(处方状态异常无法发药); } // 2. 更新处方状态 prescription.setStatus(已发药); prescriptionMapper.updateById(prescription); // 3. 扣减库存 ListPrescriptionItem items prescriptionItemMapper.selectByPrescriptionId(prescriptionId); for (PrescriptionItem item : items) { Drug drug drugMapper.selectById(item.getDrugId()); int newStock drug.getStock() - item.getQuantity(); if (newStock 0) { throw new BusinessException(药品库存不足 drug.getDrugName()); } drug.setStock(newStock); drugMapper.updateById(drug); } }Transactional(rollbackFor Exception.class)这句很关键不加的话代码抛了运行时异常MyBatis 更新却已经提交了就会出现上面说的“处方已发药、库存没减”的数据不一致。业务异常BusinessException也要让它能被事务捕获所以 rollbackFor 指定为 Exception.class而不是默认的 RuntimeException。挂号、退号也一样。退号除了修改挂号状态还需要把对应的收费记录作废或者退款这两步操作也必须在同一个事务里。事务控制在医院系统里应用场景特别多我建议写代码前先梳理每个业务操作涉及几张表的变更涉及多表的统一加事务注解。4.3 前端项目创建与联调前端我用 Vite 创建 Vue3 项目命令是npm create vitelatest hospital-web -- --template vue。安装完依赖后把 Element Plus、Vue Router、Pinia、Axios、ECharts 依次装好。然后创建 src 下的目录结构把路由、状态管理、请求封装这些基础代码搭好。联调阶段最有价值的工具是浏览器的 Network 面板和后端的 SQL 日志。我在调试时发现的问题大多可以通过这两个工具定位。比如前端报错 404先去 Network 里看请求的 URL 是否正确如果 URL 没问题但后端报 500就看 IDEA 控制台的 MyBatis 日志基本上能直接看到是哪一条 SQL 出了问题。联调时还要注意一个细节前端请求的 BaseURL 统一写/api后端 Controller 的 RequestMapping 也统一以/api开头。这样开发时通过 Vite 代理把/api转发到后端端口生产时后端 Tomcat 直接接收/api请求前端代码和后端接口在整个生命周期内不需要任何调整。4.4 打包部署的完整流程本地开发跑通后打包部署的流程很固定后端打包用 Maven 生命周期里的package命令生成 jar 包。执行java -jar hospital-system.jar即启动。如果服务器端口被占用要先用netstat -ano | findstr 8080Windows或lsof -i:8080Linux检查是什么进程占用了端口。前端打包前要确认 axios 的 baseURL 是相对路径/api而不是http://localhost:8080/api这样的绝对路径否则部署到服务器上时接口地址写死会导致前端页面发的请求全部打向本地这是新手最常见的部署事故。打包后把 dist 里的 static 和 index.html 复制到 SpringBoot 的 resources/static 下。需要提醒的是如果后端代码有改动要重新打包 jar 并把前端 dist 再次复制进去如果前端频繁改页面也可以选择把前端单独部署到 Nginx反向代理转发/api到后端的 8080 端口这种方式对前后端后续独立升级更友好医院这类需要长期维护的系统我推荐用 Nginx 方案。5. 常见问题与排查技巧实录5.1 MySQL 连接与数据导入问题MySQL 连接问题在我接触这个项目的咨询里出现频率最高。最常见的两个报错一是Access denied for user rootlocalhost这通常是密码错误或者账号不允许当前主机登录检查连接串里的用户名密码即可二是Public Key Retrieval is not allowed这个是因为 MySQL 8 默认使用 caching_sha2_password 认证方式连接串里要加上allowPublicKeyRetrievaltrue。我之前遇到过很多次所以推荐你不管用 MySQL 5.7 还是 8.0都在连接串里把 useSSL、serverTimezone、allowPublicKeyRetrieval 这三个参数一次配齐减少后续麻烦。还有一个经常出现的问题是中文乱码。前端页面提交中文名称后数据库里存的是问号原因多半是数据库连接串少了characterEncodingutf8或者建表时字符集没设置 utf8mb4。解决方法是连接串加上编码参数同时在建表语句里显式指定DEFAULT CHARSETutf8mb4。5.2 MyBatis 扫描不到 Mapper 和 SQL 打印问题项目启动后如果报Invalid bound statement (not found)通常是两个原因Mapper 接口没有被 Spring 扫描到或者 XML 文件没有放到 mapper-locations 指定的路径下。解决办法是启动类上加MapperScan(com.example.hospital.mapper)再确认 XML 文件路径和 application.yml 里配置的mapper-locations一致。如果 XML 文件放在 resources 目录下构建后能被正确复制如果放在 java 包目录下则需要额外在 pom.xml 里配置资源打包规则这里建议统一把 XML 放到 resources/mapper 目录下最省心。SQL 不打印的问题也很常见。有人发现控制台只能看到接口调用日志看不到 SQL 语句实际上只要在 MyBatis 配置里加上log-impl: org.apache.ibatis.logging.stdout.StdOutImpl控制台就会打印出执行前的完整 SQL 和参数列表这对排查动态 SQL 拼接错误极其有帮助。如果你用的是 Druid 连接池可以同时开启 Druid 的 SQL 过滤日志但说实话 StdOutImpl 已经足够日常开发用了。5.3 SpringBoot 版本太高导致的兼容性问题有一个热搜词叫 “springboot版本太高”这个确实值得单独讲。SpringBoot 3.x 出来后很多人直接在 Initializr 选了新版本结果发现两个典型问题第一javax.* 包全部换成了 jakarta.* 包原来的javax.servlet、javax.validation相关依赖全部报错需要修改 import第二部分第三方 starter比如某些 MyBatis 旧版、Druid 旧版在 Jakarta 环境下兼容性不好要么启动报错要么运行时异常。解决方案是如果不是非用不可的新特性SpringBoot 项目建议使用 2.7.x 版本配合 Java 8生态最成熟稳定。如果你已经创建了 3.x 项目就要把 mybatis-spring-boot-starter 换成最新版 3.x 对应的版本Druid 也换到最新版同时把 import javax 改成 import jakarta。5.4 Vue 打包后刷新 404 与跨域失效问题前端打包后放入 SpringBoot 静态目录点击按钮能正常跳转但按 F5 刷新当前页时报 404这个我上面提到过是 Vue Router history 模式导致的。解决办法二选一改成 hash 模式createWebHashHistory或者后端加一个路径转发配置。我个人的话做这种后台管理系统都是直接改成 hash 模式一方面部署简单另一方面不会出现静态资源路径 404 的问题代价是 URL 多一个 # 号对后台系统来说完全不是问题。跨域失效问题一般出在 Nginx 部署场景。前端和后端部署在同一台服务器的不同端口时浏览器页面请求后端接口会跨域。我在前后端分离部署时会在 Nginx 配置里加一层反向代理把/api/请求转发到http://127.0.0.1:8080前端页面所有请求依然走/api相对路径不产生跨域问题。如果一定要前后端不同域名直接调用就在后端写一个 CorsFilter 配置类但相对路径代理才是我更推荐的实践。5.5 排查问题时的通用思路最后分享一个通用的排查思路也是我带着几个新手做完整个项目后的经验总结。遇到任何报错不要慌按顺序看三个地方第一看浏览器 F12 的 Network 面板接口请求有没有发出、返回什么状态码第二看后端 IDEA 控制台有没有异常堆栈、SQL 日志输出是否正常第三看数据库里的数据当前记录的状态字段是不是符合预期是不是上一步操作没有正常更新。这三个地方逐一排查80% 的问题都能解决掉。千万不要拿到报错就立刻去搜索引擎找结果先看懂自己的请求链路和数据状态培养这种排查思维比记住任何一个具体报错都重要。我在实际做这个项目的过程中体会最深的一点是技术本身并不难SpringBoot 和 Vue 都是非常成熟的框架真正的难点在于业务状态的流转设计和对细节的把控。比如发药和扣库存必须在一个事务里、退号要联动收费记录、前端路由守卫要和后端权限保持一致这些细节才是决定一个系统能不能真正用起来的关键。后来我又把这个项目的思路迁移到了别的管理系统上你会发现核心架构是完全可以复用的无非是业务表和数据字典换了一茬Controller 和 Service 的分层逻辑几乎原封不动。最后再分享一个算是我私人偏好的小技巧无论前端还是后端写接口或页面的时候先想清楚这个操作的状态变更链路也就是用户点了这个按钮后数据库里有哪几张表要改、各自改成什么状态写出来的代码会比你想一步做一步流畅得多。这也是我建议所有做这类全栈项目的同学刻意训练的一种思维方式。