十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot MyBatis 全栈实战:家政管理平台设计与实现

Spring Boot MyBatis 全栈实战:家政管理平台设计与实现 1. 家政管理服务平台问题拆解与技术选型思路做这个项目之前我先在小区业主群和几家本地保洁公司里转了一圈发现一个很基础但特别浪费人力的问题用户约保洁靠打电话、加微信服务人员排单靠手写表格或者群消息喊话结算又靠月底对账漏单、撞单、扯皮的情况非常普遍。所以我想做一个轻量级的家政管理服务平台核心就是三件事把用户的下单入口做成线上化把服务人员的排班接单做成可视化把订单和结算的数据做成可追溯。这也决定了这个项目不是单纯的技术 demo而是以业务闭环为导向的真实管理系统。技术栈我最终选择了 Spring Boot 2 MyBatis 这个组合前端配合 Vue 3 全家桶整个项目从前端页面到后端接口、从数据库表到部署脚本都由自己一个人完成算是一次比较完整的全栈之旅。我特意没有用 Spring Boot 3原因后面会详细解释简单说就是生态兼容和稳定性的取舍。先说为什么很多实际项目会用 Spring Boot 2 MyBatis而不是一味追新。这个平台要管理用户、订单、人员、结算查询条件是动态的业务规则天然复杂MyBatis 的灵活 SQL 正好契合这种“按条件拼查询”的场景。而 Spring Boot 2 经过多年迭代在 JDK 8 环境下运行稳定第三方组件分页插件、代码生成器、各种 starter的兼容性早就被验证得彻彻底底踩坑成本低。对于中小型管理系统来说稳定性和可维护性永远排在架构潮流前面。另外我也把“为什么不用 MyBatis-Plus”这个决策简单说明一下。我承认 MyBatis-Plus 在 CRUD 上做得足够好但这个项目的核心是让我把 MyBatis 本身吃透包括动态 SQL、缓存机制、拦截器原理、手写复杂关联查询这些基础能力恰恰是日常面试和后续接手老项目时最被考验的点。所以这个项目里我刻意保留了原生 MyBatis 的大量用法只在分页和代码生成上借助了通用工具。换言之MyBatis-Plus 是站在 MyBatis 肩膀上的框架但你得先会走再学跑。整个系统按角色拆成三个端端使用者核心功能用户端 H5/小程序普通用户浏览服务项目、提交预约、在线支付定金、查看订单状态、评价服务服务端管理后台企业管理人员服务人员管理、排班设置、订单指派、订单审核、财务结算服务人员端移动端页面复用保洁/家政人员查看排班、确认接单、开始/完成服务、上传服务凭证这样的角色划分决定了后端需要一个统一的 RBAC 权限模型不能是简单的“登录即通过”。入口权限、数据权限、操作权限要分开控制。我在设计时基于 Spring Security JWT 做了一层轻量级鉴权具体实现会放到后面的模块里讲。2. 数据库设计订单状态机和数据表解构家政平台的表结构说实话不算复杂但订单表是整张业务网的中心订单状态机一定要在数据库设计阶段就想清楚否则后端的 if-else 会越写越乱。我定义订单状态如下待支付用户提交预约生成订单但未支付待指派已支付等待管理员指派服务人员已指派管理员已指派人员等待服务人员确认待服务服务人员已确认等待上门服务中服务人员点击开始服务待验收服务人员点击完成等待用户确认已完成用户确认完成订单关闭可评价已取消用户取消或超时未支付退款中取消后涉及退款流程针对已支付订单这个状态机用一张整数状态字段维护配合一个状态流转日志表记录谁在什么时间把订单从什么状态改成了什么状态出现纠纷时可以快速回溯。这里有个宝贵的教训状态字段不要用字符串描述比如“已完成”、“已完成2”整数枚举配合代码常量类管理是最可靠的业务名称变了只改常量类的映射就行。主表设计的核心几张表用户表memberid、手机号、密码摘要、昵称、头像、家庭地址 JSON 串、注册时间。地址用 JSON 存的原因是可以存多个常用地址一条记录搞定。服务人员表workerid、姓名、手机号、服务区域编码、技能标签、状态空闲/忙碌/休息、评分。这里引入了“服务区域编码”字段为后面做“附近派单”预留了基础用简单的区域编码匹配而不是复杂的地理位置计算。服务项目表service_itemid、类目名称、单位、定价方式按次/按小时、建议时长、描述。定价这里后来用了 price_config 关联表因为不同城市、不同服务级别定价不一样。订单主表ordersid、订单号、用户id、服务项目id、服务人员id、预约日期、开始时间、时长、地址快照、金额快照、状态、支付方式、备注。写订单金额快照很重要因为服务项目价格后续可能调整而已经生成的订单必须展示用户下单那一刻的价格所以订单表里有商品名称快照、单价快照、总价快照而不是通过 join 查最新价格。2.1 订单号生成一次生产环境踩坑后的修正早期我图省事订单号直接用yyyyMMddHHmmss 随机三位数测试环境数据量小看不出问题。后来模拟并发下单时发现同一秒内生成两个订单的概率非常高导致订单号重复。后来改成“时间戳 用户ID后四位 随机四位 业务前缀”也还是会撞。最终我用的是数据库自增主键反转法先插入一条订单记录拿到自增 id再用 id 当天日期拼接订单号。每次生成订单号看起来不够“高并发高大上”但完全够用且绝不重复。对于家政平台这种低并发业务自增主键反转是最省心、最容易理解的方式。2.2 服务人员推荐与排班逻辑的简化处理做“推荐服务人员”功能时我先在数据库层面做了基础过滤指定日期 指定时段 服务区域 技能匹配拿这些条件去 worker 表和排班表查。排班表worker_schedule设计为“按天一条”每天有开始时间、结束时间、时段容量字段。派单时检查该人员当天是否已有占用订单用 count 统计。很可惜后来我发现这种排班设计过于理想化实际中服务人员经常临时请假、换班所以后来在排班表增加了“调班单”和“请假单”两个关联表。这里想说的是初期 MVP最小可行产品不需要把排班做到算法级别的优化先保证人工可干预后续有了数据积累再做智能派单这是非常务实的路径。3. 后端核心实现Spring Boot 2 与 MyBatis 的整合细节3.1 Maven 依赖选型与版本锁定Spring Boot 2.7.18 是我个人比较推荐的 Spring Boot 2 收官版本它把大量已知漏洞和 bug 做了修复同时保留了 javax 命名空间的兼容。在 pom 里我锁定了如下核心依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies一个值得注意的点mybatis-spring-boot-starter2.3.x 版本对应 MyBatis 3.5.x和 Spring Boot 2.7 配合很顺畅。pagehelper 插件我选定 1.4.7是因为它对 MyBatis 3.5 的拦截器机制兼容得比较好不会出现“分页查询返回结果不生效”这种奇怪问题。3.2 application.yml 配置背后的含义MyBatis 相关配置我这样写mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.housekeep.entity configuration: map-underscore-to-camel-case: true cache-enabled: false call-setters-on-nulls: false log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case开启后数据库的order_no字段能自动映射到 Java 类的orderNo属性省去大量手写resultMap的时间。cache-enabled: false这里我先关闭了 MyBatis 的二级缓存后面排查问题时会详细说明原因。log-impl配置成 StdOutImpl 之后控制台能直接打印完整 SQL 和参数对于单机开发调试非常方便。3.3 Mapper 接口与 XML动态 SQL 的实战写法这个项目中订单查询是最复杂的功能因为订单列表要同时支持按用户手机号模糊查、按订单号精确查、按状态查、按时间范围查、按服务项目查而且这些条件是组合出现的。用注解方式写会很别扭所以订单模块我用了 XML 方式select idselectOrderList resultTypeOrders SELECT o.id, o.order_no, o.member_id, o.worker_id, o.status, o.appointment_date, o.amount, o.payment_method, o.create_time, m.phone AS member_phone, w.name AS worker_name, si.item_name AS service_name FROM orders o LEFT JOIN member m ON o.member_id m.id LEFT JOIN worker w ON o.worker_id w.id LEFT JOIN service_item si ON o.service_item_id si.id where if testorderNo ! null and orderNo ! AND o.order_no #{orderNo} /if if testmemberPhone ! null and memberPhone ! AND m.phone LIKE CONCAT(%, #{memberPhone}, %) /if if teststatus ! null AND o.status #{status} /if if teststartDate ! null AND o.appointment_date gt; #{startDate} /if if testendDate ! null AND o.appointment_date lt; #{endDate} /if /where ORDER BY o.create_time DESC /select这里有两个容易踩的坑我给第一次写 MyBatis 动态 SQL 的朋友提个醒和在 XML 里必须转义成lt;和gt;否则 XML 解析直接报错。#{字段}是预处理参数可以有效防止 SQL 注入${字段}是字符串拼接除非是动态表名、排序字段这种无法用占位符的场景否则不要用。实际项目里很多 SQL 注入事故就是滥用${}导致的。3.4 Service 层事务边界设计一个典型的“下单”操作在 Service 层涉及四步写入插入订单主表、生成订单状态流转记录、扣减服务人员当日时段容量、如果是新用户则写入用户表。这四个动作必须在一个事务里否则可能出现“订单生成了排班容量没扣导致重复预约”的问题。我用Transactional(rollbackFor Exception.class)来声明事务边界并且强调 rollbackFor 一定要写因为 Spring 默认只在遇到 RuntimeException 时回滚而业务方法里可能抛出检查异常不加 rollbackFor 会造成异常消息返回给前端数据库却已经提交了这是非常隐蔽的 bug。有个细节要注意事务方法最好不要在同一个类里被另一个方法直接调用因为 Spring 的Transactional是基于 AOP 代理实现的同类内的this.method()调用不会经过代理对象事务就失效了。这个“事务自调用失效”问题面试里被问的频率相当高。3.5 拦截器MyBatis 层面的三个实用技巧MyBatis 的拦截器机制是我这次刻意研究的部分因为它不仅是分页插件的基础也能为项目提供统一的“无侵入”能力。我实际动手写了一个公共字段自动填充拦截器解决创建时间、更新时间、操作人这三个字段的自动赋值问题Intercepts({ Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}) }) public class AuditFieldInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms (MappedStatement) invocation.getArgs()[0]; Object parameter invocation.getArgs()[1]; if (parameter null) { return invocation.proceed(); } SQLCommandType sqlCommandType ms.getSqlCommandType(); if (sqlCommandType SQLCommandType.INSERT) { setField(parameter, createTime, new Date()); setField(parameter, updateTime, new Date()); } else if (sqlCommandType SQLCommandType.UPDATE) { setField(parameter, updateTime, new Date()); } return invocation.proceed(); } }这个拦截器基于一个前提所有实体类统一继承 BaseEntityBaseEntity 里定义了 createTime、updateTime、remark 这些公共字段。这样一来任何新加的业务表只要继承 BaseEntity插入和更新时就自动带上了审计字段代码里不再需要手写创建时间赋值。这里有第二个提醒实现 MyBatis 拦截器之后必须检查 SQL 命令类型并且不能用反射直接改参数对象因为分页插件也是拦截 Executor 的如果两者叠加执行的顺序可能造成参数信息丢失。我的做法是拦截器里只做“有则赋值”不做结构变更把冲突降到最低。4. 全栈实现权限认证、统一 API 与前端对接4.1 JWT 认证流程从用户登录到接口鉴权这个项目用户、管理员、服务人员三个角色共用一套登录入口后端通过账号类型字段区分。登录成功之后后端签发 JWTpayload 里包含 userId、userType、过期时间用 HMAC256 算法签名。前端拿到 token 后放在本地存储每次请求在 axios 拦截器里自动带Authorization: Bearer token。后端需要做两件事一个登录接口负责签发 token一个拦截器负责校验 token。拦截器是整个鉴权的核心我写了一个 OncePerRequestFilter 继承 Spring Security 的过滤器链避免每个请求都执行多次Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader request.getHeader(Authorization); if (StringUtils.hasText(authHeader) authHeader.startsWith(Bearer )) { String token authHeader.substring(7); try { Claims claims Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody(); request.setAttribute(userId, claims.get(userId)); request.setAttribute(userType, claims.get(userType)); } catch (JwtException e) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write(token invalid or expired); return; } } filterChain.doFilter(request, response); } }我在这里特意做了一个设计决定每个请求把 userId 和 userType 放到 request attribute 里而不是放到 ThreadLocal 静态变量。虽然 ThreadLocal 拿用户信息方便但异步请求、线程池复用时会带来线程隔离问题请求处理完还要手动 remove很容易内存泄漏。4.2 统一返回结构与全局异常处理前后端对接时最怕的就是接口数据格式不统一。有的接口返回{code: 200, data: ...}有的接口返回{success: true, result: ...}前端要写一堆适配逻辑。我从一开始就约定所有接口统一返回{ code: 0, message: success, data: {} }code 为 0 表示成功非 0 表示业务错误码。业务错误码我维护在一个枚举类里比如 1001 表示“用户不存在”、1002 表示“订单状态已变更请刷新后重试”、1003 表示“服务人员当日排班已满”。全局异常处理用RestControllerAdvice统一拦截这一层非常省心。业务异常BizException直接返回错误码和提示信息参数校验异常MethodArgumentNotValidException返回字段级错误兜底异常返回通用“系统繁忙请稍后重试”。任何一层没被 try-catch 包住的异常最终都会落到这里绝不会出现堆栈信息直接返回到前端的情况。4.3 前端选型与管理后台页面实现管理后台我使用 Vue 3 Vite Element Plus 开发。为什么选 Vue 而不是 React一方面 Vite 搭建速度快组件生态对后台管理系统非常成熟另一方面这个项目是个人全栈练手Vue 的模板语法更直观后端出身的人上手成本更低。前端除了用户 H5 页面核心是管理后台的几个主要模块仪表盘显示今日订单数、待指派订单数、服务人员在线数。这个页面接 3 个统计接口无所谓性能但接口设计上我顺手做了聚合一次请求返回所有统计数字减少请求次数。订单管理页核心表格 查询表单 操作按钮。表格列不允许一次全查后端只返回当前页的 20 条操作按钮根据订单状态动态显示比如“待指派”状态的订单才能看到“指派人员”按钮。服务人员管理页列表 弹出编辑框 技能标签。前端路由用 vue-router 做了权限控制根据登录用户角色动态生成路由菜单。这个逻辑实现了信息层级隔离比如服务人员登录后看不到财务结算菜单。4.4 前后端联调跨域、代理与接口规范开发环境下前端在 5173 端口后端在 8080 端口跨域是绕不开的问题。我的处理分两步第一步Vite 配置 devServer proxy所有 /api 开头的请求代理到 http://localhost:8080。第二步后端 CORS 配置只允许http://localhost:5173这个来源确保开发环境能调通生产环境则通过 Nginx 反向代理统一域名避免跨域问题扩大化。联调时我发现接口规范必须写成文档不然一天能被“这个接口参数到底叫 memberId 还是 userId”的问题折腾死。我维护了一份 POSTMAN 接口集合所有 mock 数据、参数示例、响应示例都放在集合里。后续加功能时前端照着集合写请求后端照着集合改接口谁改了结构另一方马上就能发现。5. 常见问题与排查技巧实录这部分是这次全栈开发中我实打实踩过的坑整理成速查表对使用 Spring Boot 2 MyBatis 的同学应该会有直接帮助。5.1 MyBatis 缓存问题排查记录项目里有一段时间出现“修改了订单状态但前端查询列表仍然是旧状态”的诡异问题。排查链路最终锁定了 MyBatis 的二级缓存。缓存的标准机制是一级缓存是 SqlSession 级别的默认开启二级缓存是 namespace 级别的默认不开启但如果你在某个 Mapper XML 里加了cache/二级缓存就全局生效了。问题是订单表的 Mapper 开启了二级缓存而订单状态流转是通过另一个 Mapper 操作的两个 Mapper 都属于不同 namespace缓存没有自动失效于是读到了脏数据。我的解决方案很简单全局关闭二级缓存cache-enabled: false业务系统一般不建议开启二级缓存因为数据一致性很难保证。真正要做缓存我会用 Redis 做业务级缓存并显式地控制缓存失效时机而不是把整张表的缓存交给 MyBatis 托管。5.2 SQL 日志打印与 IDEA 插件辅助排查 SQL 问题时单靠控制台打印的Preparing:和Parameters:信息也能看但如果 SQL 比较复杂参数一多肉眼对照起来很吃力。我在 IDEA 里装了两个插件MyBatis Log Plugin 和 MyBatisX。MyBatis Log Plugin 可以把参数直接拼接到 SQL 语句中还原出可执行 SQL直接拷贝到 Navicat 里跑这对定位“为什么条件查不出数据”效率极高。MyBatisX 则提供了 Mapper 接口和 XML 之间的跳转功能点击方法名直接跳到对应 XML 标签在代码量大的项目里非常方便。5.3 N1 查询问题的发现与解决订单列表里要展示服务人员姓名如果只在查询订单列表时查出 workerId再循环去查 worker 表就会产生 N1 次查询页面响应时间会随数据量线性增长。我用 MyBatis 的日志功能观察实际 SQL 执行数量后发现这个问题然后改成 JOIN 查询一次取回所有需要的字段。更稳妥的方案是采用延迟加载在 XML 里配置association selectxxx lazy-loadtrue但延迟加载也有坑如果查询连接不关闭Session 结束之后再去读取懒加载属性会报 LazyInitializationException。对于中小型项目我的经验是“尽量一次 JOIN 查完不要搞延迟加载花活”。5.4 前端转全栈时最容易犯的接口设计错误如果你正在从纯前端转全栈我最想提醒你的一件事是不要在接口里直接返回实体类。一开始图省事订单接口直接把 Orders 实体序列化返回结果实体里有密码摘要字段用户表、手机号全字段、内部备注字段全部暴露给了前端。后来统一改成 VOView Object根据前端展示需要来定义字段比如订单列表 VO 只有 orderId、orderNo、serviceName、amount、status 这些必要字段。这个改动带来的好处是前后端职责清晰后端可以自由调整表结构而不影响前端展示。5.5 常见问题速查表问题现象可能原因解决方案查询结果没有按条件过滤if判断条件写错test 中用了而不是eq或空字符串判断少了检查 test 表达式建议打印完整 SQL 确认条件拼接更新操作不生效但也没报错没有加Transactional或者事务方法同类自调用按章节 3.4 的说明补上事务并避免同类内自调用返回给前端的时间少了 8 小时JSON 序列化时区问题在 application.yml 配置spring.jackson.time-zone: GMT8并统一使用 LocalDateTime同一个订单被两个用户同时抢到下单时没有加锁或没有状态校验在 Service 层先查再更新的同时在数据库层加版本号或者WHERE status1条件分页插件报页面越界异常前端传 pageNum 从 0 开始PageHelper 从 1 开始前端统一从 1 开始或后端拦截小于 1 的页码并修正为 15.6 动态 SQL 的一个隐蔽坑example.and 条件拼接很多同学习惯用 MyBatis 的 Example 类构造查询条件但 Example 的and和or嵌套复杂的时候容易拼出错误的 SQL。比如想实现“A 且B 或 C”的条件如果连续调用example.andEqualTo(B)和example.orEqualTo(C)生成的 SQL 是A AND B OR C而不是A AND (B OR C)查出来的数据范围就错了。我建议复杂条件一律用 XML 动态 SQL 手写用wherechooseforeach组合控制力强排查也直观。Example 类只适合最简单的单表等值查询。6. 性能优化与安全加固的实战经验项目上线之后订单列表接口在数据量到 5 万条时出现了明显的响应变慢。我加了索引优化和 SQL 调优这里分享几个关键优化点。6.1 索引设计与慢查询分析订单表最常用的查询条件组合是status appointment_date我建了组合索引(status, appointment_date)。另外用户端的“我的订单”查询条件是member_id create_time也建了组合索引(member_id, create_time)。MySQL 的索引优化有个原则组合索引最左前缀原则。索引(status, appointment_date)可以服务status ?的查询也可以服务status ? AND appointment_date ?的查询但不能单独加速appointment_date ?的查询需要单独建索引。我在优化时用EXPLAIN命令查看每一条慢查询的key_len和possible_keys确认索引真的被用到。另一个容易被忽视的点是对orders.create_time这样的字段如果只做时间范围查询索引效率不错但如果写WHERE DATE(create_time) 2025-01-01因为对索引列使用了函数索引会失效查询变成全表扫描。正确写法是WHERE create_time 2025-01-01 00:00:00 AND create_time 2025-01-02 00:00:00。6.2 安全加固密码存储与接口防刷用户密码我使用 BCryptPasswordEncoder 进行哈希存储不使用任何可逆加密算法。登录时用校验方法比对哈希值数据库里就算泄露拿到手的是不可逆的密文安全性优于 MD5/SHA1这些算法已经能被彩虹表快速破解而且没有加盐机制。接口防刷这块我做了简单的请求频率限制同一个用户 ID 在 1 分钟内的下单请求不能超过 5 次。实现上用了 Spring Boot 拦截器 ConcurrentHashMap 计数器。虽然分布式环境下应该用 Redis 计数但单机项目这个方案已经能挡住绝大部分恶意刷单。6.3 数据库连接池参数调优数据库连接池选择 HikariCPSpring Boot 2 默认的一开始用的默认配置压测时遇到一个现象偶尔有请求等待数据库连接超时。后来调整了连接池参数spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000这里的核心经验是连接池大小不是越大越好。个位数的数据库实例连接数过大反而增加数据库切换和锁竞争开销。20 个连接对于家政管理平台这个并发量已经非常充裕。加大连接池设置时记得同步检查 MySQL 服务端的max_connections配置否则连接池申请超过 MySQL 上限服务端直接拒绝。7. 部署上线与全流程复盘7.1 服务器部署与 Nginx 配置部署环境我用了一台 2 核 4G 的云服务器操作系统 Ubuntu技术栈组合为 Nginx Spring Boot Jar MySQL 8.0。后端打包用的是 Maven打出的 fat jar 约 80MB启动命令如下nohup java -Xms256m -Xmx512m -jar housekeep-server-1.0.0.jar \ --spring.profiles.activeprod \ application.log 21 -Xms和-Xmx设成 256m/512m是因为这台服务器内存有限而且单机项目的并发量完全用不到太大的堆内存。JVM 堆内存设太大反而导致内存不足系统会频繁触发 SWAP性能更差。Nginx 配置需要注意几件事前端静态文件用 gzip 压缩JS/CSS 文件缓存设置expires 7d接口/api反向代理到本地 8080 端口。数据库每天凌晨 2 点自动备份用 cron 定时任务执行 mysqldump备份保存最近 7 天的文件。7.2 前端部署与接口域名处理前端打包产物直接上传到服务器/usr/share/nginx/html目录。接口请求地址需要区分环境和域名的两种方式开发环境用相对路径/api走 Vite 代理生产环境用相对路径/api走 Nginx 反向代理。这样前端不用维护多套环境变量打包统一发布时不需要改代码。7.3 上线初期的稳定性观察上线后前三天是最容易出现问题的阶段。我每天看三样东西应用日志的 ERROR 关键字、慢查询日志、订单状态处理的异常告警。家政平台有一个特殊场景凌晨的预约单用户晚上 8 点下单第二天早上 9 点服务服务人员可能漏看接单通知。我补了一个定时任务每天早上 8 点扫描所有“待确认”状态的订单给对应服务人员发一条短信提醒这是一个不影响技术架构但对用户体验很有价值的业务功能。7.4 回顾整个项目的执行顺序现在回头看整个项目能按期推进得益于把工作拆分成了清晰的阶段需求分析阶段确认三个角色和核心流程产出角色矩阵和订单状态机。数据库设计阶段先画 ER 图把表关系理清楚再写代码。后端骨架阶段搭建工程、配置依赖、实现基础 CRUD、统一响应体。业务闭环阶段完成下单、派单、服务、验收这条主线流程。前端开发阶段管理后台和用户 H5 同步推进。联调优化阶段处理跨域、字段一致、异常展示。部署上线阶段购买服务器、配置环境、数据备份、上线监控。这样的顺序保证了任何时候项目都处于一个“可运行”的中间状态而不是最后几天才把所有模块拼起来。8. 给后来者的一些实在建议这个项目做到最后给我最大的感受是全栈开发的最大瓶颈从来不是某一项技术不会而是如何在多个技术栈之间穿梭时不丢失上下文。你在写前端页面时要记得后端接口的字段命名你在调后端接口时要记得前端组件的数据结构你在设计数据库时要想着后续报表统计怎么查。这种“带宽消耗”是真实存在的所以一定要把接口文档和数据库设计文档放在随手能翻到的地方不要依赖记忆。另外如果你也想从零开始做一个 Spring Boot 2 MyBatis 的全栈项目我建议你按下面这个顺序来学习和实践第一阶段把 MyBatis 基础打牢。手写动态 SQL理解参数映射和结果映射搞明白一级缓存、二级缓存的机制差异和适用场景。这个阶段不要用 MyBatis-Plus把原生能力吃透。第二阶段掌握 Spring Boot 的核心机制。自动配置、Starter 原理、Spring Security 过滤器链、Transactional 事务传播行为。这些知识不仅在面试中被高频考察在实际排查问题时更能派上大用场。第三阶段前后端联调。先学任意一个前端框架把 CRUD 页面搭通然后关注接口设计规范和异常处理。这个阶段你会发现 90% 的问题不是语法问题而是数据契约不一致的问题。第四阶段部署与监控。至少把项目部署到一台真实的云服务器上配置 HTTPS、Nginx、数据库备份、日志切割。这些“脏活累活”才是衡量一个项目能否真正落地的标准。我最后想补充的一个小技巧是开发过程中遇到不确定的业务规则时先写最简单的实现不要过早引入设计模式或分布式组件。家政平台的订单状态机初期非常复杂但我坚持先按照最直接的 if-else 写等流程跑通了再做抽象和重构。这样做的优势是整个系统随时处于可用状态不会因为过度设计而陷入“代码写得漂亮但业务跑不通”的尴尬境地。如果你顺着这条路径走下来并且真的把一个家政管理服务平台从前端页面到后端接口、从数据库到部署上线完整做了一遍你收获的将不只是 Spring Boot 2 和 MyBatis 的语法熟练度而是对整个软件交付流程有了系统性的掌控感。这种掌控感是刷再多教程题都换不来的。
返回列表