苍穹外卖项目优化方向

1 阅读20分钟

苍穹外卖项目接口文档&整体项目优化方案文档

基于黑马程序员苍穹外卖(SpringBoot+Mybatis‑Plus+Vue),包含:现存问题场景例子、优化原因、技术选型、改造实现思路、技术选型理由、B站学习博主推荐、学习周期预估。分为接口层优化、业务逻辑层优化、数据库层优化、安全优化、性能优化、工程代码规范优化、前端Agent智能化优化、测试&文档优化八大模块。

目录

  1. 接口文档层面优化
  2. 接口性能优化
  3. 数据库层优化
  4. 安全防护优化
  5. 业务代码工程化优化
  6. 缓存落地优化
  7. 分布式问题优化(集群部署)
  8. 前端+Agent智能化优化
  9. 各个技术栈B站博主&学习时间

1. 接口文档层面优化

现状

项目原始使用YApi生成接口文档,缺少:统一响应枚举、错误码完整定义、入参校验说明、接口权限说明、请求头token说明、示例请求响应、版本管理;没有OpenAPI(Swagger)注解,项目启动直接访问文档,不需要登录YApi平台;YApi属于独立平台,代码修改后需要人工同步YApi,经常出现代码改了,YApi文档没更新,文档和代码不一致。

问题场景通俗例子

场景1:后端修改员工新增接口,把phone手机号最大长度改为11位,但是忘记登录YApi修改文档。前端开发照着旧YApi文档开发,提交12位手机号,后端直接报错,前后端联调排错花30分钟。 场景2:线上报错code=0成功,code=1失败,但是YApi没有定义各个code业务含义;前端拿到code=1 msg=密码错误,另一个接口code=1 msg=账号已禁用,前端同学不知道code=1代表什么业务,只能去翻后端代码。 场景3:所有接口都需要携带token请求头,YApi文档没有标注,新手测试接口,直接postman调用,报401,不知道需要加token。

优化点优化原因技术选型怎么优化实现为什么选该技术
接入Knife4j(Swagger3 OpenAPI)1.代码注解驱动,代码即文档,修改代码文档自动更新,杜绝文档和代码不同步; 2.项目启动直接访问页面,不需要第三方平台; 3.支持注解标注权限、请求头、示例、枚举; 4.可以直接页面调试接口Knife4j 4.x(SpringBoot3) / Knife4j 3.x(SpringBoot2)1.引入maven依赖; 2.编写配置类开启Knife4j; 3.Controller、实体类加上注解@Schema; 4.统一标注接口权限,需要token; 5.自定义枚举注解,把业务错误码全部写进文档; 6.给请求参数、返回值增加示例值1.Swagger原生注解繁琐,Knife4j是国内增强版,UI好看; 2.完全兼容OpenAPI3; 3.支持生产环境关闭文档,安全; 4.无需部署第三方服务
统一业务错误码体系当前只有code 0成功、1失败,没有细分业务码,前端无法根据code做业务分支,只能解析msg字符串,msg一旦修改前端就会bug自定义全局业务枚举 + 全局异常处理器1.新建枚举ResultCodeEnum,区分: 成功20000,登录失败20001,账号锁定20002,参数校验失败40000,权限不足40100,数据不存在40400; 2.全局异常处理器抛出业务异常携带枚举; 3.接口文档展示全部业务码; 4.前端根据业务code做逻辑,不再依赖msg文本1.解耦前后端,msg可以做展示,code做逻辑判断; 2.线上日志可以打印业务错误码,方便定位bug;
请求头Token强制标注到文档所有管理端、用户端接口都需要JWT token,原始文档没有说明Knife4j @RequestHeader注解配置全局参数在Knife4j配置类配置全局请求头Authorization,所有接口文档自动展示需要携带token测试人员、前端一看文档就知道必须带token,减少401问题

补充:保留YApi作为二次备份,但是以代码内Knife4j为唯一真实文档,代码修改文档自动更新。

2. 接口性能优化

现状

原始苍穹外卖接口:

  1. 分页接口(套餐分页、菜品分页)没有限制最大pageSize;用户传pageSize=10000,数据库一次性查询一万条,内存占满,接口超时。
  2. 返回大量冗余字段,例如员工查询接口返回password密码字段给前端;
  3. 部分接口循环查询数据库N+1问题;
  4. 返回时间格式不统一,有的返回时间戳,有的返回yyyy‑MM‑dd HH:mm:ss,前端反复处理时间。
问题场景通俗例子

场景1:恶意用户调用套餐分页接口,page=1&pageSize=99999,直接查询数据库全表,数据库CPU飙升,整个系统接口全部卡死。 场景2:查询员工信息接口,返回password明文给到前端接口返回值,虽然前端不用,一旦抓包泄露用户密码,重大安全隐患。 场景3:查询订单列表,循环遍历每个订单再去查询用户信息,10条订单就访问数据库10次,N+1查询,接口响应慢。

优化点优化原因技术选型怎么优化实现选型理由
分页参数防护,限制pageSize最大值防止超大pageSize,全表扫描压垮数据库SpringMVC拦截器 / @Valid自定义校验注解自定义注解@MaxPageSize(max=100),对分页查询pageSize校验,超过100强制设置为1001.注解声明式,侵入业务代码少; 2.全局生效,所有分页接口统一防护
DTO/VO分层,入参DTO,出参VO,不直接返回数据库Entity实体原始直接把数据库Entity返回前端,容易泄露敏感字段password;Entity数据库字段改动直接影响接口返回DTO(接收前端参数) VO(返回前端)1.新增DTO接收接口入参; 2.VO作为返回对象,过滤password、is_delete等数据库内部字段; 3.Entity只和数据库打交道,不对外暴露; 4.使用MapStruct做Entity、DTO、VO之间转换MapStruct编译期生成转换代码,反射零开销,性能远高于BeanUtils,类型安全,编译报错,不会出现字段名写错线上才发现
N+1查询改造,改用连表查询/批量查询循环查询数据库导致接口慢Mybatis‑Plus QueryWrapper,批量查询订单列表不要循环for里面查询用户,一次SQL批量查询用户id集合,内存组装数据,减少数据库IO减少数据库交互次数,数据库IO是主要性能瓶颈;
全局统一Jackson时间序列化原始接口时间格式五花八门Jackson + @JsonFormat + application.yml全局配置application.yml统一配置日期时间序列化格式,LocalDateTime统一返回yyyy‑MM‑dd HH:mm:ss;禁止返回时间戳;所有接口时间格式统一,前端不用每个接口单独处理时间字符串;

3. 数据库层优化

现状

  1. 原始项目数据库缺少索引,很多查询条件没有索引,大表查询全表扫描;
  2. 没有逻辑删除,业务做删除直接物理删除,线上数据误删无法恢复;
  3. 没有数据库事务传播、隔离级别精细化控制;部分业务少加事务;
  4. 没有大字段优化(套餐的setmeal_dish、订单详情,避免select * 查询全部大字段);
  5. 数据库没有慢SQL监控。

场景通俗例子 场景1:根据用户名查询员工登录,username没有索引;员工表几十万条,登录接口全表扫描,登录接口非常慢。 场景2:管理员误操作点击批量删除套餐,数据库直接物理删除,想要恢复数据只能去备份文件,很麻烦。 场景3:新增套餐,插入套餐主表成功,插入套餐菜品中间表失败,主表数据残留,产生脏数据,没有事务回滚。

优化点优化原因技术选型怎么优化实现选型理由
增加业务查询索引无索引大表全表扫描,接口响应慢MySQL B‑Tree索引1.员工表:username建立唯一索引; 2.订单表:user_id、order_time建立复合索引; 3.套餐表:category_id,status索引; 4.菜品表:category_id,status索引; ⚠️不要建立过多索引,索引会降低插入更新性能B‑Tree适合等值、范围查询,外卖业务绝大多数都是这类查询
逻辑删除物理删除误删数据不可恢复Mybatis‑Plus逻辑删除插件全部业务表增加字段 is_delete tinyint(1) default 0; Mybatis‑Plus开启逻辑删除; 调用removeById逻辑标记删除,数据库数据保留数据保留,便于排查线上问题;误删可以后台恢复;
业务事务精细化控制部分业务缺少事务,出现部分成功部分失败脏数据Spring声明式事务 @Transactional,指定传播行为、回滚规则新增套餐:新增套餐主表+批量插入套餐菜品关系,加上@Transactional(rollbackFor = Exception.class); 订单下单:扣库存、生成订单、生成订单明细,整体事务; ⚠️避免大事务,不要把查询逻辑放入事务Spring声明式事务简单,注解开发;rollbackFor指定全部异常,默认只回滚RuntimeException,很多业务异常不会回滚
禁止select * 查询避免查询不需要的大字段,IO开销大Mybatis‑Plus指定select列,不使用QueryWrapper查询全部列查询套餐列表,只查询需要展示字段,不要查询描述、图片大文本字段;详情接口再查询全部字段减少网络传输、内存占用,提升查询速度;
慢SQL监控线上不知道哪些SQL执行慢P6Spy / Druid监控集成Druid监控页面,开启慢SQL记录;超过500ms打印慢SQL日志;Druid连接池自带监控,无需额外引入组件,直接监控SQL执行时间,方便优化慢SQL

4. 安全防护优化

现状原始项目安全短板

  1. JWT没有设置过期刷新机制,token一旦泄露,只能等待过期,用户只能重新登录;
  2. 缺少接口防刷,暴力破解登录;恶意频繁调用下单接口;
  3. XSS、SQL注入防护薄弱;
  4. 缺少接口权限校验:例如普通员工可以调用删除员工接口,只靠前端隐藏按钮,后端没有鉴权;
  5. 文件上传接口没有校验文件后缀、大小;可以上传jsp/php恶意脚本文件;
  6. 密码明文存储(原始项目密码是MD5,但是没有加盐!)。

通俗问题场景例子 场景1:黑客拿到管理员JWT token,token有效期7天,7天之内黑客都可以操作后台,用户无法主动下线。 场景2:黑客循环调用登录接口暴力破解管理员账号密码,每秒100次请求,系统没有限制。 场景3:文件上传接口,上传后缀改为.jsp的木马文件,访问木马获取服务器权限。 场景4:数据库密码直接MD5不加盐;黑客拿到数据库,彩虹表直接破解全部密码。

优化点优化原因技术选型怎么优化实现选型理由
JWT + Redis实现token黑名单、支持主动下线、刷新token原生JWT无状态,无法主动让token失效;泄露风险高JJWT + Redis1.accessToken短期2小时;refreshToken7天; 2.登出时accessToken放入Redis黑名单;拦截器校验黑名单; 3.支持刷新token,不用重复登录;JWT无状态,搭配Redis解决无法主动失效的短板;
接口限流防刷防止暴力登录、恶意频繁请求Redis + 自定义注解 + Lua脚本自定义注解@RateLimiter,登录接口限制:同一个IP一分钟最多5次;下单接口限制同一个用户1秒最多1次;Redis+Lua保证限流原子性,防止并发超量;注解方式业务代码低侵入;
密码加密加盐存储原生MD5无加盐,彩虹表破解SpringSecurity BCryptPasswordEncoder密码存储使用BCrypt自动加盐;登录直接调用matches方法校验密码;BCrypt自带随机盐,抗彩虹表;行业标准密码加密方案;
RBAC后端接口鉴权(不仅仅前端控制)当前项目只有简单登录,没有角色权限,普通员工可以调用管理员接口SpringSecurity / Sa‑Token使用Sa‑Token,区分管理员、普通员工角色;接口注解@SaCheckRole("admin"),后端校验角色;Sa‑Token上手简单,对比SpringSecurity配置量少;适合外卖业务;
文件上传接口安全校验防止上传恶意脚本文件上传校验1.限制文件大小; 2.校验后缀只允许jpg、png; 3.重命名文件,不使用原始文件名; 4.文件存储路径和tomcat运行目录隔离,不能直接执行脚本;阻断上传木马攻击;
XSS防护前端传入脚本存入数据库,页面渲染执行脚本Jackson反序列化转义字符;输入过滤请求参数过滤< > &等特殊脚本字符;防止存储型XSS攻击;

补充:苍穹原始项目没有RBAC权限,只有登录;真实企业项目后端绝对不能依靠前端隐藏按钮做权限,后端接口必须校验。

5. 业务代码工程化优化

现状

  1. Controller层写大量业务逻辑;Service接口和实现类,部分业务Service方法非常长,几百行代码;
  2. 大量重复try‑catch,到处手写返回Result;
  3. 没有全局异常处理器,很多接口捕获异常直接返回Result.error("失败"),丢失异常堆栈信息;
  4. 魔术字符串、魔术数字到处写,例如状态1起售 0停售直接硬编码写代码;
  5. 没有统一返回对象Result的工具方法,到处new Result。

通俗场景例子 场景1:controller里面写订单业务逻辑,controller代码几百行;后期改需求,分不清哪些是参数处理,哪些是业务逻辑,维护痛苦。 场景2:代码到处写if(status ==1),1代表起售;过半个月开发忘记1代表啥,到处看代码。

优化点优化原因技术选型怎么优化实现选型理由
分层严格遵守:Controller只做参数接收、调用Service、组装返回;业务全部放Service;复杂业务拆分Manager层Controller厚重,维护困难Spring分层架构Controller:接收DTO,调用Service,返回VO; Service:业务逻辑; Manager层:跨多个Service复杂业务(下单); DAO/Mapper:数据库操作单一职责;便于单元测试;业务逻辑复用;
全局异常处理器 @RestControllerAdvice每个接口写try‑catch,大量重复代码;异常丢失堆栈,线上排查难@RestControllerAdvice + 自定义业务异常BusinessException1.全局捕获所有业务异常、参数校验异常、系统异常; 2.统一封装Result返回;日志打印完整异常堆栈; 3.业务代码直接throw new BusinessException(枚举),不需要try‑catch去除重复try‑catch,统一异常处理;线上日志完整堆栈,快速定位bug;
业务常量、枚举替换魔术数字硬编码数字,可读性差,修改需要全局搜索替换Java枚举订单状态、菜品状态、套餐状态全部新建枚举;代码全部使用枚举,禁止直接写数字0、1IDE可以自动提示;修改只改枚举一处;代码可读性高;
Result返回工具类封装到处new Result,重复代码工具类ResultUtilsResultUtils.success(data),ResultUtils.fail(枚举),统一构造返回对象;减少重复对象创建,代码整洁;
参数校验JSR‑303 @Valid当前很多入参判断手写if‑else判断参数非空;代码臃肿Hibernate ValidatorDTO字段注解@NotBlank @NotNull @Pattern;Controller加上@Valid;全局异常捕获校验异常自动返回提示声明式校验,去除大量手写if判断;校验逻辑集中在DTO;

6. 缓存落地优化 Redis

现状

原始苍穹外卖没有使用Redis,频繁查询分类、套餐、菜品;每次请求访问MySQL数据库;高并发场景数据库压力巨大。

通俗场景例子:首页用户端查询菜品分类,几千用户同时访问首页,每次请求查询MySQL;数据库压力高。

优化点优化原因技术选型怎么优化实现选型理由
热点数据缓存:分类、起售套餐、起售菜品高频查询,数据更新频率低,大量请求打到MySQLRedis + SpringCache注解1.@Cacheable缓存查询分类、套餐列表; 2.新增/修改/删除操作使用@CacheEvict清空对应缓存; 3.设置过期时间; ⚠️不缓存订单这类高频变化数据SpringCache注解式缓存,业务代码改动很小;Redis高性能;减轻MySQL压力;
店铺营业状态放入Redis店铺营业状态查询非常频繁,很少修改Redis字符串修改营业状态更新Redis;查询直接读Redis;减少数据库访问;

注意:缓存要处理缓存穿透、缓存击穿、缓存雪崩问题;空值缓存、互斥锁、过期时间随机偏移。

7. 分布式问题优化

原始项目单体架构,只适合本地演示;如果部署多台服务器集群,会出现问题:

  1. JWT登录,存储在服务器内存;做负载均衡,用户请求落到不同服务器,登录失效;
  2. 定时任务多台服务器同时执行,重复下单、重复统计报表;
  3. Session不共享(本项目用JWT,没有session,但是定时任务是大坑)

通俗场景例子:两台服务器集群;定时任务统计营业额,两台机器同一时间都执行统计,统计逻辑执行两次,数据重复。

优化点优化原因技术选型怎么优化实现选型理由
JWT登录信息存入Redis,无状态集群兼容内存存储登录信息,集群部署登录错乱JJWT + Redis用户登录信息放入Redis;所有服务器读取同一个Redis;集群环境登录正常;支持主动下线token;
分布式定时任务多台实例定时任务重复执行XXL‑JOB分布式任务调度把原来Spring @Scheduled迁移到XXL‑JOB;只需要一台执行任务;避免定时任务重复执行;任务可以后台管控、日志查看、失败重试;
分布式锁例如库存扣减,集群并发会超卖Redis Redisson分布式锁扣减库存业务加分布式锁,防止并发超卖Redisson封装好锁,自动续期,避免死锁;

8. 前端+Agent智能化优化

原始前端Vue只是简单页面;可以结合AI Agent做智能化;

  1. Agent接口调试助手:对接大模型,自动解析Knife4j接口文档;输入自然语言,自动生成接口测试请求;例如:“调用新增员工接口,姓名张三,手机号13800138000”,Agent自动生成post请求;
  2. 前端表单AI辅助:新增菜品页面,输入菜品描述文字,AI自动生成菜品口味;
  3. 错误提示Agent:前端请求报错,把错误信息丢给大模型,给出排错建议。
优化点优化原因技术选型实现思路选型理由
AI Agent接口调试助手测试接口需要手动写参数,效率低OpenAI/通义千问 + Knife4j OpenAPI后端把OpenAPI JSON给到大模型Function‑call;自然语言转HTTP接口调用提升开发测试效率;
前端AI辅助表单新增菜品写口味麻烦Vue + 大模型API输入菜品简介,调用大模型生成口味列表自动填充表单提升运营人员操作效率;

9. 各个技术栈B站博主推荐以及推荐学习时间

前提:已经掌握Java基础,Maven、MySQL基础。

技术B站推荐博主/课程推荐学习周期备注
SpringBoot2 / SpringBoot3(苍穹外卖核心)黑马程序员《SpringBoot3全套教程》5‑7天对应苍穹外卖底层;重点掌握:配置、全局异常、AOP、拦截器
Mybatis‑Plus尚硅谷 MyBatis‑Plus全套教程2‑3天CRUD、条件构造器、逻辑删除、分页插件
Redis完整黑马程序员Redis全套3‑4天5大数据类型;缓存三大问题;Lua脚本;SpringCache;
Knife4j(Swagger3)搜:Knife4j官方教程0.5天快速上手配置注解
Sa‑Token权限框架芝麻粒儿(Sa‑Token官方B站账号)1‑2天登录、角色鉴权、token刷新、限流
MapStruct搜:尚硅谷 MapStruct0.5‑1天DTO VO Entity转换
Druid监控、P6Spy慢SQLB站搜对应快速教程0.5天数据库监控
XXL‑JOB分布式定时任务尚硅谷XXL‑Job教程1‑2天分布式定时任务
BCrypt密码加密黑马安全教程片段0.25天
Vue2/Vue3前端(苍穹外卖前端)黑马程序员Vue2全套5‑7天苍穹外卖前端Vue2
Agent大模型Function‑call开发尚硅谷大模型应用开发3‑5天做AI Agent接口助手

学习顺序建议

  1. SpringBoot → Mybatis‑Plus → Redis → Sa‑Token权限 → Knife4j文档 → MapStruct → Druid数据库优化 → XXL‑JOB → Agent大模型应用。

补充总结

原始黑马苍穹外卖课程是教学演示版本,为了方便学生看懂,牺牲很多企业级安全、性能、健壮性;课程适合入门学习业务;如果要把项目改造成企业可上线项目,上面的优化点大部分都需要做。 优先级排序(从最重要到次要):

  1. ✅全局异常 + DTO/VO分层 + JSR‑303参数校验(低改动,收益极高)
  2. ✅Knife4j接口文档,统一业务错误码
  3. ✅数据库索引 + 逻辑删除 + @Transactional事务完善
  4. ✅密码BCrypt加盐、文件上传安全校验
  5. ✅Redis热点缓存
  6. ✅Sa‑Token登录鉴权,token黑名单,防刷限流
  7. ✅分布式锁+XXL‑JOB(集群部署才需要,单体演示可以延后)
  8. ✅AI Agent智能化(锦上添花,业务稳定之后再做)

如果你需要,我可以帮你生成:

  1. 部分核心代码示例(全局异常处理器、Knife4j配置、枚举、DTO‑VO MapStruct示例)
  2. 或者输出Markdown直接复制到笔记软件的精简版优化清单。