ARTICLE DETAIL

资讯详情

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

家政保洁预约系统的Spring Boot实战:订单状态机与调度架构

家政保洁预约系统的Spring Boot实战:订单状态机与调度架构 1. 项目概述与核心价值1.1 为什么需要一个家政保洁预约系统家政保洁这个行业说起来门槛不高但真正做起来全是细节。传统模式下用户要打电话预约、商家要手动记单、保洁员要等调度通知整个链路全靠人工在微信和电话之间来回倒腾。单量少的时候还能勉强撑住一旦规模上来漏单、撞单、派错人几乎不可避免。我做这个基于 Spring Boot 的家政保洁预约系统目标很直接把“用户下单—平台接单—调度派单—保洁员上门—服务完成—费用结算”这条完整链路全部线上化用一套系统替代过去的人工登记本和微信群通知。这个定位决定了项目的核心不是一个简单的信息展示网站而是一个带状态流转、带订单管理、带角色权限的真实业务系统。如果你现在正打算做 Spring Boot 方向的毕业设计或者你本身就在小型的本地生活服务团队里做开发这个项目的拆解思路值得你完整看一遍。它覆盖了 Spring Boot 开发中最常见的那些能力点用户认证授权、RESTful API 设计、MyBatis 数据持久层、文件上传、定时任务、消息通知哪怕把里面的业务换掉这套骨架也完全可以复用到别的管理系统上。1.2 系统是给谁用的我不太建议一上来就闷头建表写代码先把用户角色理清楚后面所有的功能和接口设计都会顺很多。这个系统里一共有四类角色各自的需求非常明确普通用户注册登录、浏览服务项目、选择保洁人员、下单预约、在线支付或到付、查看订单进度、提交评价和投诉。保洁人员接单、查看当天任务安排、更新服务状态、查看自己的结算收入。平台管理员审核保洁人员入驻、配置服务项目和价格、处理订单异常和退款申诉、查看经营数据报表。系统运营人员可并入管理员角色处理用户反馈、发布公告、管理优惠活动。我当时做角色规划的时候刻意把保洁人员单独拆成一个端而不是让管理员代为操作。原因很简单每个保洁员一天跑五六单如果每单都要管理员手动改状态这个系统的效率优势就荡然无存了。让一线人员直接在手机上操作数据才能实时回流。2. 技术选型与架构设计思路2.1 Spring Boot 3 JDK 17 还是 Spring Boot 2 JDK 8动手之前最纠结的就是版本选择。网上大量资料还是基于 Spring Boot 2.x 写的很多毕设项目也默认用 JDK 8。我最后选了 Spring Boot 3.2 JDK 17理由有三个。第一Spring Boot 3 已经发布两三年了生态上主流的中间件客户端都已经完成了适配遇到问题能搜到的解决方案并不少。第二Spring Boot 3 全面拥抱 Jakarta EE对于一些还在 JDK 8 上挣扎的老项目来说这是个学习新规范的好机会。第三JDK 17 的虚拟线程虽然在 Spring Boot 3.2 里还不是默认启用的但只要你愿意配置一个spring.threads.virtual.enabledtrueTomcat 处理请求的线程模型就能切换到虚拟线程这对保洁预约这种大量短请求、IO 密集型场景的提升非常明显。不过这里有个必须提醒的坑如果你选 Spring Boot 3那 MyBatis 的适配包不要用mybatis-spring-boot-starter的老版本要用mybatis-spring-boot-starter3.x 版本或者直接用mybatis-spring手动装配否则启动的时候会报各种奇怪的类找不到错误。而且 Spring Boot 3 的自动配置类命名方式从org.springframework.boot.autoconfigure变成了新的包结构你如果习惯看源码一开始会有点不适应。2.2 核心依赖清单整套系统的依赖管理我列在这里照着配基本不会出错parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent dependencies !-- Web 层 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据持久层 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 安全认证 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency !-- Redis 缓存与分布式锁 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 接口文档 -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.5.0/version /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这套组合里有两个选型值得特别说一句。第一个是安全框架用了 Spring Security 而不是自己写拦截器。自己写拦截器看起来简单但后面做 token 刷新、接口权限分级、密码加密的时候你很快就发现还是在重复造 Spring Security 的轮子不如一开始就建立正确的抽象。第二个是接口文档直接上了 springdoc-openapiSwagger 页面访问路径是/swagger-ui.html后端接口写完自测和联调效率提升很明显。2.3 项目结构怎么分包我看到很多同学喜欢用controller/service/mapper三层包结构走天下小项目确实没毛病但家政预约系统里涉及多种角色、多个核心业务这种基础三层结构到后面就会变成一个大杂烩。我的分包策略是这样的com.example.homemaid ├── common // 通用类统一返回体、全局异常、常量、工具类 ├── config // 配置类Security、Redis、MinIO、MyBatis、异步线程池 ├── controller // 接口层按业务域拆分 │ ├── auth │ ├── order │ ├── user │ ├── cleaner │ └── admin ├── service // 业务层 │ ├── auth │ ├── order │ ├── dispatch │ └── payment ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 接口入参出参 ├── vo // 视图对象 └── job // 定时任务按业务域分包最大的好处是代码可定位性变强了。比如你要改派单逻辑直接进dispatch这个 package里面从接口到实现到数据访问一应俱全不需要在几十个 service 文件里来回跳转。3. 核心功能模块与数据库设计3.1 需求拆解核心业务其实是“订单状态机”家政保洁预约系统的业务核心说白了就是订单状态如何一步步流转。我把这个状态机作为整个系统的中轴来设计所有模块都围绕它展开。一个普通订单的生命周期如下待支付 - 待派单 - 已派单 - 服务中 - 已完成 └- 已取消 └- 退款中 - 已退款每个状态之间的转换动作触发对应的业务逻辑。比如“已派单”到“服务中”保洁员需要点击“开始服务”这时系统要校验保洁员是否为该订单的指派人员同时记录开始服务的时间这个时间直接影响后续的服务时长结算。再比如“已完成”状态用户才能提交评价如果尝试对未完成订单评价后端会返回明确的业务错误码。这个状态机在设计时有几个边界情况必须提前想清楚用户支付超时订单创建后 15 分钟内未支付自动取消并释放保洁员档期。派单失败首选保洁员不在线或拒单系统需要自动按优先级交给第二顺位的保洁员。服务完成但用户未确认超过 24 小时自动确认完成避免订单永远卡在“待确认”状态。3.2 核心数据表设计数据库我用 MySQL 8.0字符集 utf8mb4存储引擎 InnoDB。核心表一共七张我挑几张关键的展开说。用户表userCREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, openid varchar(64) DEFAULT NULL COMMENT 微信小程序openid, phone varchar(20) DEFAULT NULL COMMENT 手机号, password varchar(128) DEFAULT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role tinyint NOT NULL DEFAULT 0 COMMENT 0-用户 1-保洁员 2-管理员, status tinyint NOT NULL DEFAULT 1 COMMENT 1-正常 0-禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;用户表设计上有个小技巧手机号和密码设为可空这样就可以兼容微信小程序授权登录和账号密码登录两种方式。用户第一次用微信授权登录时手机号为空在下次预约时再引导绑定手机号。预约订单表orderCREATE TABLE order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号业务维度的唯一标识, user_id bigint NOT NULL, cleaner_id bigint DEFAULT NULL COMMENT 接单保洁员, service_item_id bigint NOT NULL COMMENT 服务项目, address varchar(255) NOT NULL COMMENT 服务地址, contact_name varchar(20) NOT NULL, contact_phone varchar(20) NOT NULL, service_time datetime NOT NULL COMMENT 预约的上门时间, service_duration int NOT NULL DEFAULT 2 COMMENT 服务时长小时, amount decimal(10,2) NOT NULL COMMENT 订单金额, pay_type tinyint NOT NULL DEFAULT 0 COMMENT 0-在线支付 1-到付, status tinyint NOT NULL DEFAULT 0 COMMENT 0-待支付 1-待派单 2-已派单 3-服务中 4-已完成 5-已取消 6-退款中 7-已退款, remark varchar(500) DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_cleaner_id (cleaner_id), KEY idx_status (status), KEY idx_service_time (service_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表的关键不在字段多少而在索引的设计。查询维度无非是“我的订单”和“我的派单”所以user_id和cleaner_id必须建索引按状态筛选列表也很常见status单列索引就够用。预约时间的范围查询加上索引可以避免后续做“按天看排班”时全表扫描。这里有个很容易忽略的点订单号不用自增 id而是单独生成业务订单号。我用的生成规则是yyyyMMddHHmmss 用户 id 后四位 随机四位数字虽然不能保证绝对唯一但在单机场景下冲突概率极低配合唯一索引兜底就够了。服务项目表和保洁员档期表也比较关键。服务项目表就是id、名称、分类、价格按小时/按次、时长、图片、描述。档期表记录保洁员未来 7 天的可接单时段一个大字段存 JSON 数组即可简单场景不用搞太复杂的排班算法。3.3 分布式锁在派单环节的应用派单是并发风险最高的操作。同一个时段两个用户同时预约系统通过定时任务或即时调度分配保洁员时如果并发处理不当就会出现同一个保洁员被同时派给两单。Spring Boot 的Transactional只能保证数据库事务的原子性解决不了跨请求的并发覆盖问题。我用 Redis 分布式锁来保证分配操作串行化try { String lockKey dispatch:lock: cleanDate; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(10)); if (!locked) { throw new BizException(当前派单操作繁忙请稍后重试); } // 执行派单逻辑 } finally { redisTemplate.delete(lockKey); }注意锁的粒度。如果用全局锁整个平台的派单都串行化性能浪费按日期作为锁 key同一时间只会锁住那天的派单操作既保证了同一保洁员的排他分配又不影响其他日期的单子实测效果很好。4. 关键技术点落地认证、文件上传与任务调度4.1 JWT 登录认证与 Spring Security 配置认证方案我选择了 JWT Spring Security。整个认证链路是这样的用户登录成功 → 后端验证账号密码 → 签发 JWT token → 前端后续请求在 Header 中带上Authorization: Bearer token→ 后端过滤器解析 token 并设置认证信息。Spring Security 6 的配置写法跟 5 有较大差异关键是过滤链的构建方式。一个核心配置类骨架如下Configuration EnableWebSecurity EnableMethodSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf - csrf.disable()) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**, /api/service-item/list, /doc.html, /webjars/**, /v3/api-docs/**).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .requestMatchers(/api/cleaner/**).hasRole(CLEANER) .anyRequest().authenticated() ) .exceptionHandling(handler - handler.authenticationEntryPoint(customAuthEntryPoint)) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }几个我在实际配置中踩过的点CGLIB 代理问题。Spring Boot 默认对EnableMethodSecurity开启的是代理方式如果你的 Security 配置里直接用某个 service 内部方法调用另一个被PreAuthorize注解的方法代理会失效权限校验直接绕过。解决办法是不要在同类内部调用被权限注解的方法或者直接注入代理对象。token 过期时间。保洁员一天在外跑单手机解锁频率高token 如果 30 分钟就过期用户就一直在重新登录的路上。我把 access_token 的过期时间设为 24 小时同时提供一个 refresh_token 接口用于无感续期。密码加密。不允许明文密码入库使用BCryptPasswordEncoder同密码每次加密结果不同防止撞库。4.2 基于 Redis 的验证码与缓存设计短信验证码是预约系统中影响用户体验的重要环节尤其是上门保洁类的服务用户手机号填写错误会直接导致无法联系。我用 Redis 存验证码并设置过期时间key 的规则是sms:code:{phone}有效期 5 分钟。发送前先查一下有没有未过期的验证码有则提示用户不要频繁请求防止短信接口被刷。除了验证码Redis 还承担了三处缓存职责服务项目列表低频变动但高频读取缓存 30 分钟。保洁员当前在线状态用 Redis Set 存储在线保洁员 id方便派单时快速筛选可接单人。订单状态计数管理员仪表盘的待处理订单数、今日已完单数等聚合数据每 5 分钟刷新缓存避免每次都去 MySQL 做 count 聚合。4.3 MinIO 处理资质图片与头像上传服务项目中需要上传图片保洁员实名认证需要上传身份证照片这些都涉及文件存储。我没有把文件存到本地磁盘或直接塞数据库 BLOB 字段而是接入 MinIO 对象存储。MinIO 是目前最流行的开源对象存储方案安装简单、有可视化管理界面、接口跟 S3 完全兼容非常适合中小型项目自托管。Spring Boot 集成 MinIO 的核心配置minio: endpoint: http://127.0.0.1:9000 access-key: your-access-key secret-key: your-secret-key bucket-name: homemaid-images对应的 Java 配置类Configuration public class MinioConfig { Value(${minio.endpoint}) private String endpoint; Value(${minio.access-key}) private String accessKey; Value(${minio.secret-key}) private String secretKey; Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }上传的时候记得在application.yml里把 Multipart 文件大小限制放开spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB处理文件上传有个常见的坑如果 MinIO 中图片 URL 需要直接给前端img标签展示bucket 访问策略要设置为公开读或者通过后端接口播放文件的输入流。我采用的是生成预签名 URL 的方式保证图片不会过期失效也不会暴露 access-key。4.4 定时任务与量化保洁员收入结算系统中有几处定时任务需要处理超时未支付订单自动取消、服务完成后 24 小时自动确认、保洁员周结算。Spring Boot 自带的Scheduled注解即可满足需求不需要额外引入 Quartz。Component Slf4j public class OrderTimeoutJob { Resource private OrderService orderService; Scheduled(cron 0 */5 * * * ?) public void cancelTimeoutOrders() { ListOrder timeoutOrders orderService.listTimeoutUnpaidOrders(15); for (Order order : timeoutOrders) { try { orderService.cancelOrder(order.getId(), 超时未支付自动取消); } catch (Exception e) { log.error(取消超时订单失败订单号{}, order.getOrderNo(), e); } } } }这里有个非常重要的建议定时任务一定要逐单 try-catch。如果某一单取消失败抛出异常后续所有的订单都会被阻塞。逐单捕获异常可以保证即使有一两单处理失败其他订单的取消逻辑依然能正常执行。同时任务执行时需要打日志方便排查为什么某笔订单没有被正常取消。5. 前后端联调与部署实战5.1 后端接口设计规范项目采用前后端分离结构前端用 Vue 3 Element Plus打包后通过 Nginx 部署。前后端分离的情况下接口协议清晰与否直接决定联调效率。我的统一返回结构是这样的{ code: 200, message: success, data: { } }业务异常时 code 返回 5xx 或 4xx 业务码HTTP 状态码保持 200。这样前端拿到响应先判断code而不是拦截 HTTP 状态省去很多 axios 错误拦截的麻烦。接口风格我坚持用 RESTfulGET /api/order/{id}查订单详情、POST /api/order创建订单、PUT /api/order/{id}/cancel取消订单。动词尽量只在不得已时使用资源名称保持复数一致。5.2 Vue 与 Spring Boot 的跨域与打包配置联调时最常遇到的问题就是跨域。我的做法是后端统一配置全局跨域而不是让前端 Nginx 去做代理转发。Spring Boot 3 的跨域配置写在WebMvcConfigurer里Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }前端 Vue 项目开发时用 Vite 代理也可以但线上部署我建议直接把前端打包产物交给 Nginx由 Nginx 静态托管同时把/api/路径通过反向代理指向后端服务。这样生产环境不存在跨域问题开发环境通过后端配置的 CORS 解决。Nginx 配置节选server { listen 80; server_name your-domain.com; root /opt/homemaid-frontend/dist; index index.html; location / { 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; } }这里有个 Vite 打包后 Vue Router 的 history 模式问题做了 SPA 路由后刷新页面会 404所以/路径的try_files必须回退到index.html这个配置不加前端路由一刷新就白屏。5.3 部署时数据库初始化和环境变量管理我使用 Docker Compose 来编排 MySQL、Redis、MinIO 和应用容器这样在一台新服务器上也能几分钟完成全套环境搭建。数据库的初始化脚本放在项目根目录的sql/init.sql中通过 MySQL 容器的/docker-entrypoint-initdb.d/目录自动执行。不同环境的配置用application-dev.yml、application-prod.yml分离数据库密码、MinIO 密钥这类敏感信息不要写死在 yml 里用环境变量注入spring: datasource: url: ${DB_URL:jdbc:mysql://127.0.0.1:3306/homemaid?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai} username: ${DB_USERNAME:root} password: ${DB_PASSWORD:root}6. 开发过程中遇到的坑与排查思路6.1 MyBatis 查询结果映射缺字段开发时遇到过 Mysql 字段service_time自动映射到 Java 实体属性serviceTime失败的问题。原因是 MyBatis 的驼峰映射默认没有开启。在application.yml中配一下就行mybatis: configuration: map-underscore-to-camel-case: true这个配置没开的话所有带下划线的数据库字段都无法映射到实体属性而且是静默失败返回全 null非常隐蔽。6.2 Spring Boot 版本过高带来的依赖兼容性问题热词里我看到“springboot版本太高”这个说法深有体会。如果你用 Spring Boot 3.3 或更新版本很多第三方 starter 会跟不上。比如老的mybatis-spring-boot-starter2.x 用 JDK 8 編译而 Spring Boot 3 要求 jakarta 命名空间直接启动直接抛NoClassDefFoundError: jakarta/servlet/ServletInputStream。解决办法有两个一是把 Spring Boot 版本固定在 3.2.x这样大部分中间件已经完成了兼容适配二是如果版本必须升级所有相关依赖按官方文档同步升级不能只改 parent 版本。6.3 订单超时自动取消与用户正在支付的冲突定时任务扫描超时未支付订单并取消但用户可能正停留在支付页支付回调刚好到达。这种情况下就会出现“订单已经被取消但支付成功”的矛盾。我的处理方式是在支付回调里做幂等校验回调到达时先检查订单状态只有状态为“待支付”的订单才更新为“已支付”如果订单已经是“已取消”状态则自动触发退款流程把金额原路退回给用户。同时在取消订单的限制里加上 5 分钟缓冲判断即支付超时判断放宽 5 分钟再执行取消避免用户在支付网关里卡了一下就被系统取消订单。6.4 MinIO 图片上传之后无法访问最典型的原因就是 bucket 的访问权限配置。默认创建的 bucket 是私有的通过 URL 直接访问会返回 403。要么在 MinIO 控制台将 bucket 的 Policy 改为download即公开读要么使用presignedGetObject()生成有时效的访问 URL。对于家政平台这种用户头像和服务图片需要广泛展示的场景我建议直接公开读图片链接持久有效不会被缓存失效问题困扰。6.5 定时任务在同一时刻重复执行多实例部署或者长时间运行后同一个Scheduled任务在集群场景下每个节点都会执行一次用户就会收到两次取消通知或两次结算短信。单机部署问题不大一旦后面拆多实例部署就得上分布式任务锁用 Redis 的setIfAbsent TTL 实现一个简单的任务锁抢不到锁的实例直接跳过本次执行。这个方案简单、轻量不需要引入 XXL-Job 那样重型框架在目前这个体量下完全足够。7. 实测经验与性能优化心得7.1 从一次压测看到的问题系统基础功能跑通之后我用 JMetter 做了 300 并发下的接口压测。问题很快暴露出来创建订单接口在并发升到 300 时平均响应时间飙升到 3000ms 以上TPS 只有不到 80。定位后发现瓶颈根本不在数据库而在createOrder方法里同步调用了金额计算、库存查询等多个外部服务接口。解决思路是把创建订单主流程里非核心的操作异步化。比如通知保洁员、下发消息通知这类操作放进异步线程池Async(orderAsyncPool) public void sendNotify(Order order) { // 短信通知、IM通知等 }同时启动类或配置类开启EnableAsync并自定义线程池配置核心线程数设为 10最大线程数 20队列容量 200拒绝策略用CallerRunsPolicy。改造之后接口响应时间降到 400ms 以内压测通过。7.2 慢查询优化的一些经验订单列表页打开慢最直接的手法就是看EXPLAIN查看 SQL 执行计划。我当时发现idx_status虽然建了索引但效果不理想。原因是status字段的区分度太低状态就 0-7 那么 8 个值选择性差的索引对 MySQL 优化器来说宁愿全表扫也不要走索引。处理方式有两种一是查询列表时强制使用user_id或cleaner_id索引先圈定语料范围再筛选状态二是如果业务上经常按“状态 服务时间”组合过滤就建立一个联合索引(status, service_time)让索引同时覆盖两个维度。7.3 事务与锁的使用细节Transactional的坑在于它默认只在 RuntimeException 时回滚如果你在事务方法里抛出一个自定义的 checked exception事务不会回滚数据已经写进去了才报错这会让用户以为操作失败但实际上已生效。我的统一做法是自定义BizException继承RuntimeException所有业务校验失败直接抛它既能被全局异常处理器捕获返回友好提示又能保证事务正常回滚。锁的使用也有讲究更新订单状态这种写操作用数据库select ... for update可以直接锁行防止状态被并发修改但要注意锁必须置于事务中且在事务提交后释放在方法内部获取锁而方法结束前提交事务的写法容易造成死锁。8. 复盘与若干建议如果把这套家政保洁预约系统的开发过程做一次复盘我个人觉得最有价值的并不在于用了多少新技术而是把业务状态、角色边界、并发安全这些软件工程里最朴素的命题扎扎实实地落地了一遍。这个系统麻雀虽小五脏俱全从面向用户的预约下单到运营侧的派单调度再把支付、评价、结算、定时任务串起来基本上把 Spring Boot 开发者在中小型业务系统中最常遇到的技术场景都覆盖了。最后给准备在这个方向上继续扩展的同学提几个可以落地的方向。一是接上真实支付渠道微信支付/支付宝这会让项目从“演示品”变成“可用产品”但要注意支付回调的签名验证和幂等处理。二是把派单从简单规则升级为一个轻量的调度引擎比如引入评分机制综合考虑保洁员评分、距离、忙闲程度来做自动派单。三是增加一个运营后台的数据看板用 ECharts 的折线图和柱状图展示每日订单量、营收趋势这些数据能直观说明系统上线后的业务增长。系统的骨架和基建已经在这里了剩下的就是根据你服务的真实场景去填充业务细节。这一套做下来你会对 Spring Boot 的理解比看任何教程都深刻得多。
返回列表