Cyan Blog Cyan Blog
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
  • 简历项目
  • Java面试题

  • 后端开发
  • 数据库面试题
  • Maven
  • MyBatis面试题
  • Spring面试题
  • SpringBoot面试题
  • SpringCloud面试题

  • Redis面试题

  • 设计模式
  • 中间件

  • 场景题
  • Other

  • Random

  • 项目面试题

    • 黑马点评面试题
    • 苍穹外卖
      • JWT令牌的结构
      • 项目中的自定义拦截器是怎么实现的
      • ThreadLocal存储用户信息时,如何避免内存泄漏问题?
      • 为什么选择JWT而不是传统的Session机制
      • 如何确定哪些商品属于"热点商品"的
      • Spring Cache的核心注解有哪些?各自的作用是什么?
      • 如何实现条件化缓存
      • 你们项目是怎么解决缓存击穿问题的
      • 请说明如何实现公共字段的自动填充
      • SpringTask定时任务
      • 使用RabbitMQ处理超时订单(项目优化)
      • WebSocket
    • +
    • 项目linux部署
  • 面试
  • 项目面试题
2025-04-22
0
0
目录

苍穹外卖

  • 项目描述:本项目是一款外卖订单管理系统,是基于SpringBoot实现,包括后台管理端和用户端两部分。管理端可以对商品的分类、订单、员工等信息进行管理维护,统计各类数据,以及发放优惠卷; 用户可以通过此平台浏览菜单、下单支付,并跟踪订单状态。餐厅端则可以接收订单、管理库存,并安排配送。支持商家管理、用户下单、支付及数据可视化。
  • 技术栈:Spring Boot、MySQL、Mybatis、Redis、JWT、WebSocket、Swagger、Nginx
  • 工作职责:
    • 使用JWT令牌技术和自定义拦截器实现用户无状态认证,并使用ThreadLocal存储用户信息;
    • 使用Redis缓存热点商品信息,减少数据库查询压力,接口响应时间降低约30%;
    • 采用“缓存预热+双删策略”解决缓存击穿问题,QPS提升40%;
    • 利用AOP+自定义注解 实现公共字段自动填充(如创建时间、更新时间),减少冗余代码;
    • 通过WebSocket向前端传输数据,实现来单提醒功能,以及利用SpringTask定时任务实现订单的超时处理,超时自动取消订单;
    • 通过Nginx反向代理实现前后端分离部署,并通过Swagger生成API文档;
    • 实现了分布式锁机制,确保订单处理的高一致性与数据的最终一致性;

使用JWT令牌技术和自定义拦截器实现用户无状态认证,并使用ThreadLocal存储用户信息;

# JWT令牌的结构

  • Header:包含算法和类型(如 {"alg": "HS256", "typ": "JWT"})。
  • Payload:包含用户信息和其他数据(如 {"sub": "123", "name": "John"})。
  • Signature:对 Header 和 Payload 的签名,用于验证 Token 的完整性。

# 确保JWT令牌的安全性

防止令牌被盗用或重放攻击

使用https通过加密通道传输JWT,通过将JWt存储在Cookie中,设置HttpOnly属性防止XSS脚本攻击

设置较短的 Token 有效期,减少令牌泄漏的影响,使用双token,Refresh Token通常存储在更安全的地方,Access Token即使泄漏也会很快失效。

# 项目中的自定义拦截器是怎么实现的

项目中通过实现HandlerInterceptor接口创建拦截器,核心逻辑在preHandle方法中完成JWT解析和用户认证,并将用户ID存入ThreadLocal供后续流程使用。

然后在实现了WebMvcConfigurer接口的configuration文件中,注册拦截器并指定拦截路径(如/api/**),并排除登录等接口。

# 拦截器和过滤器的区别

它们的主要区别体现在三个方面:

  1. 作用时机:过滤器在Servlet容器层面,拦截器在Spring MVC层面;
  2. 功能范围:拦截器能访问Spring Bean和Controller元数据,过滤器更接近原生Servlet;
  3. 使用场景:过滤器适合处理全局逻辑(如安全过滤),拦截器适合业务逻辑(如权限校验)。

实际项目中,我们通常用过滤器处理跨域和XSS防御,用拦截器实现JWT认证和日志记录。两者互补,共同构成请求处理链路。

# ThreadLocal存储用户信息时,如何避免内存泄漏问题?

ThreadLocal 如果使用不当,确实可能导致内存泄漏,核心原因是 ThreadLocalMap 的 key 是弱引用,而 value 是强引用,加上线程复用(如线程池),无效的 Entry 无法被及时清理。

ThreadLocal 的数据存储在 线程的 ThreadLocalMap 中,key 是 ThreadLocal 对象本身, 它是弱引用,会被 GC 回收变为 null,但 Value 仍被 Entry 强引用,导致无法回收。

当线程一直存在不被清除时,比如线程池复用时,ThreadLocalMap 会一直存在

ThreadLocal有两种情况导致OOM:

当 ThreadLocal 是局部变量,用完被清除,失去强引用,key 因弱引用被 GC 回收变为null,而value会一直存在导致OOM。 当ThreadLocal 是静态变量。虽然 key 不会被回收(静态变量是强引用),但如果线程复用时不调用 remove(),多次 set() 会导致旧 value 无法释放(例如线程池任务中重复使用同一个 ThreadLocal)。

解决方法:

threadLocalMap有清除机制,会在调用set() / get() 时自动清除key为null的数据。 但是使用ThreadLocal 时通常把它作为静态变量(即强引用),因此无法被动依靠 GC 回收,建议主动的remove 释放 key,这样就能避免内存溢出。

# 为什么选择JWT而不是传统的Session机制

JWT是无状态的,服务器无需存储任何会话状态,不需要额外查询数据库或缓存。天然支持分布式系统,利于系统服务器扩展,

# 在什么场景下JWT可能不是最佳选择?

jwt是不加密的,不能传输敏感信息,如果存储敏感数据,可以对JWT进行加密或者使用Session将敏感数据存储在服务器中,

jwt是无状态的,没有办法取消令牌强制用户下线,只能把用户状态保存下来,但也改变了它的作用

使用Redis缓存热点商品信息,并使用Spring Cache优化代码,接口响应时间降低约30%;

# 如何确定哪些商品属于"热点商品"的

我们的项目是直接将热点商品缓存在Redis中, 只有商家修改商品时会对缓存进行删除,保证商品的强一致性,通过使用Redis的全key-LFU淘汰策略来保证

# Spring Cache的核心注解有哪些?各自的作用是什么?

  • @EnableCaching: 开启缓存注解功能,通常加在启动类上

  • @Cacheable: 用于查询方法,先检查缓存,存在则直接返回,不存在则执行方法并将结果存入缓存

  • @CachePut: 总是执行方法,并将结果更新到缓存(常用于更新操作)

  • @CacheEvict: 清除缓存(常用于删除或更新操作)

# 如何实现条件化缓存

虽然项目中没有使用到,但是可以使用condition和unless属性

  • condition: 在方法执行前判断是否使用缓存
  • unless: 在方法执行后判断是否缓存结果

# 你们项目是怎么解决缓存击穿问题的

因为我们的项目只涉及一个商家,缓存的信息较少,我们的缓存没有设置TTL,所以不存在缓存击穿问题,通过LRU淘汰策略淘汰不常用的缓存

利用AOP+自定义注解 实现公共字段自动填充(如创建时间、更新时间),减少冗余代码;

# 请说明如何实现公共字段的自动填充

"我们采用AOP+自定义注解的方案,我们设置了自定义注解,主要组合了两个注解Target、Retention,标注了注解的使用范围,通过切面拦截标记了自定义注解的Mapper层方法,再通过反射,自动填充创建时间、更新时间等公共字段"

通过WebSocket向前端传输数据,实现来单提醒功能,以及利用RabbbitMQ实现订单的超时处理;

# SpringTask定时任务

在启动类上通过注解EnableScheduling开启定时任务,就可以在通过@Scheduled注解使用cron表达式设置定时任务的执行频率

# 保证集群环境下的任务不重复执行

用redis分布式锁

# 使用RabbitMQ处理超时订单(项目优化)

# 如何确保消息不丢失

消息丢失的三个途径:

生产者发送、确认消息的过程、在消息队列的丢失、消费者发送、处理、确认的过程

  • 生产者:可以通过消息重试、确认等确保消息成功到达 RabbitMQ。不过SpringAMQP提供的是阻塞式的重试,会影响业务性能,如果要使用需要配置合理的等待时长和重试次数,考虑使用异步线程来执行发送消息的代码。
  • 持久化(Durable):交换机、队列、消息都持久化,防止服务器重启丢失。
  • 消费者手动 ACK:处理完成后再确认,避免消费者崩溃导致消息丢失。

# 如何设计幂等性(Idempotency)来应对重复消息?

通过判断订单是否已经为待支付状态来实现幂等性,

# WebSocket

# 为什么选择WebSocket而不是轮询

WebSocket提供全双工通信,订单创建后立即推送,延迟低。适合高并发场景,减少服务器压力。而且订单是大部分集中在一段时间

上次更新: 2025/5/8 18:28:34
黑马点评面试题
+

← 黑马点评面试题 +→

最近更新
01
项目优化
05-06
02
项目优化
05-06
03
延迟消息
05-05
更多文章>
Theme by Vdoing | Copyright © 2025-2025 Cyan Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式