面灵AI→

汽车之家后端一面:JWT 鉴权、Java 基础与规则引擎

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

项目与鉴权

  1. 先做一下自我介绍。
  2. 挑一个你认为比较有代表性的项目/经历,详细介绍一下。
  3. 你这个项目采用了什么技术框架?主要的技术难点是什么?
  4. 为什么没有直接使用 Spring Security,而是自己做这套鉴权?
  5. 你现在这套 Token 方案在安全性上是怎么考虑的?
  6. 如果让你重新设计这套鉴权方案,你会怎么设计?
  7. JWT 的底层原理是什么?
  8. 你们现在密码是怎么存的?

AI Coding

  1. 你开发过程中主要使用哪些 AI 工具?
  2. 你平时使用 AI 辅助开发的流程是什么?
  3. AI 对你的开发效率到底有多大提升?
  4. 你怎么看 AI 对未来软件开发/岗位分工的影响?

Java 基础

  1. Java 中 == 和 equals() 有什么区别?
  2. HashMap 的底层结构是什么?
  3. JDK 1.8 对 HashMap 做了什么优化?为什么引入红黑树?
  4. 链表转红黑树的条件是什么?
  5. 单例模式怎么实现?
  6. 线程池有哪些核心参数?
  7. 线程池提交任务后的执行流程是什么?
  8. 进程和线程有什么区别?

大模型 / RAG

  1. 为什么大模型会产生幻觉?
  2. 你会怎么降低或者处理大模型幻觉?
  3. 了解 RAG 吗?

Drools / 规则引擎

  1. 你们保险理算用的是什么规则引擎?
  2. 规则发生变化的时候是怎么处理的?
  3. 你们支持动态规则配置吗?
  4. 如果预置模板不支持一种新的业务逻辑怎么办?
  5. 规则文件本质还是程序写死的,那这种方案的动态能力体现在哪里?

OpenClaw / Agent

  1. OpenClaw(龙虾)在你们公司实际是怎么应用的?
  2. 这个东西线上实际使用得多吗?实际效果怎么样?

Redis / 并发

  1. 你简历里基于 Redis SETNX 做的并发控制,具体逻辑是什么?
  2. 如果把这个场景换成商品库存,多个人同时抢一个商品,怎么避免超卖?
  3. 如果基于 Redis NX 来做,你会怎么设计?

延迟队列 / MQ

  1. 为什么要自己设计 MySQL + Redis 延迟队列?
  2. 项目已经有 Kafka,为什么不用成熟中间件?
  3. Kafka 和 RabbitMQ 你会怎么做技术选型?

MySQL

  1. MySQL 有哪些事务隔离级别?
  2. MySQL 默认隔离级别是什么?
  3. 不同隔离级别分别会出现什么问题?
  4. InnoDB 在 RR 下是怎么解决幻读的?

职场场景

  1. 如果你刚入职,领导给了一个紧急任务,但你评估自己按期做不完,你怎么办?
  2. 你会什么时候跟领导沟通?
  3. 如果你接手一个遗留系统,发现前任留下的方案不合理,但现在有紧急需求要上线,你怎么办?
  4. 这种情况下你怎么权衡重构和最小改动?
  5. 你最近在研究什么技术?

《参考解析》

为什么自研鉴权、以及 JWT 到底解决了什么。 Spring Security 提供的是完整的认证授权框架(过滤器链、会话、CSRF、方法级权限),代价是概念多、定制成本高、与前后端分离/多端登录的模型不完全贴合。自研通常是为了:无状态 Token、多端统一、细粒度权限、以及和公司已有的用户体系打通。但自研要自己补齐安全边界:Token 签名与过期、刷新机制、注销与踢下线(黑名单或版本号)、防止重放(jti + 一次性 nonce)、传输安全(HTTPS、HttpOnly Cookie 或 Authorization 头)、以及权限缓存。JWT 的本质是三段 Base64URL(header.payload.signature),payload 只做编码不做加密,任何人都能解出内容,所以绝不能放密码、手机号等敏感信息;服务端用密钥验签保证不可篡改,但无法主动失效——这就是为什么需要短过期 + refresh token + 黑名单/版本号。密码存储则必须是加盐慢哈希(bcrypt/argon2),不能 MD5,更不能明文。

HashMap 与线程池的高频细节。 JDK 1.8 的 HashMap 是数组 + 链表 + 红黑树:hash 扰动(高 16 位异或低 16 位)、按 (n-1) & hash 定位桶、链表长度到 8 且数组容量 ≥ 64 时转红黑树(小于 6 退化回链表),扩容翻倍并重新分布(1.8 用高低位链表避免重新计算 hash)。引入红黑树是为了把极端冲突下的查询从 O(n) 降到 O(log n),防御哈希碰撞攻击。线程池的核心参数是 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler;提交流程是「核心线程未满 → 建核心线程;核心满 → 入队;队列满 → 建非核心线程直到最大;再满 → 拒绝策略」。拒绝策略有 AbortPolicy(抛异常)、CallerRunsPolicy(调用线程执行,天然背压)、DiscardPolicy、DiscardOldestPolicy;生产上更常见的是自定义策略,记录日志并降级。要补一句「线程数怎么定」:CPU 密集型约 N+1,IO 密集型约 N×(1+等待时间/计算时间),但最终要靠压测。

RAG 与幻觉。 幻觉的来源:模型是概率生成而非事实检索、训练数据有噪声/过期、上下文不足时倾向编造、以及提示词诱导(要求「必须回答」)。RAG 的思路是「先检索、再生成」,把外部知识放进上下文,降低对参数化记忆的依赖。完整链路:文档解析 → 切分(按语义/标题/固定长度 + 重叠)→ embedding → 向量索引 → 查询改写 → 召回(向量 + 关键词混合)→ 重排 → 拼装上下文 → 生成 → 引用标注。降低幻觉的手段:要求模型「只依据给定资料回答,不知道就说不知道」、给出来源引用、设置相似度阈值(召回不足就不答)、多路召回 + 重排、答案后校验(规则/二次模型/事实比对)、以及把结构化查询交给工具而不是让模型算。评估上要看检索命中率、答案忠实度(是否忠于资料)、以及人工抽检,不能只看「看起来像」。

规则引擎的动态能力。 Drools 的核心是「规则与代码分离」:DRL 文件描述条件与动作,运行时编译成 Rete 网络,事实对象插入工作内存后触发匹配。动态性体现在:规则可以热更新(从数据库/配置中心加载、重新构建 KieBase)、业务人员可以改阈值与组合条件、以及同一套事实可以跑不同规则集。但「规则文件也是写死的」这个追问是对的——如果新业务需要新的数据结构或新的计算逻辑,光改 DRL 不够,仍然要写 Java 事实类或函数。所以真实方案通常是分层:简单阈值/条件走配置化规则(DB 表 + 表达式引擎,如 Aviator/SpEL),复杂流程走规则引擎,全新业务逻辑才写代码;同时要有版本、灰度、回滚、以及规则变更的测试与审计。回答时别把 Drools 说成万能,讲清「哪些能动态、哪些不能」反而加分。

Redis SETNX 防超卖的正确写法。 单纯 SETNX 只能做「占位」,不能做「扣减」,正确做法是把「判断库存 + 扣减 + 记录用户」写进一段 Lua 脚本,由 Redis 单线程原子执行:if tonumber(redis.call('get', stockKey)) > 0 then decr; sadd userKey; return 1 else return 0 end。要点:库存预热、用户去重(同一用户限购)、脚本尽量短(避免阻塞)、key 加 TTL 防泄漏、以及「Redis 扣成功但落库失败」的补偿——用可靠消息/本地消息表 + 定时对账,数据库以扣减流水为准。如果不用 Lua,至少用 DECR 后判断返回值(小于 0 再 INCR 回补),但回补期间会出现瞬时负库存,不如 Lua 干净。集群模式下 Lua 的多个 key 要在同一个 slot(用 hash tag)。

延迟队列与 MQ 选型。 自研 MySQL + Redis 延迟队列的常见理由是:订单超时关闭这类场景量不大、不想为它引入一套 MQ、以及需要可视化的任务管理(能查、能改、能重跑)。实现是「Redis ZSet 存到期时间 + 定时扫描 + MySQL 存任务详情与状态」,用 ZRANGEBYSCORE 捞到期任务、ZREM 抢占(或 Lua 保证原子),失败重试并记录。代价是精度受扫描间隔限制、可靠性要自己做(Redis 持久化、任务状态机、幂等)。已有 Kafka 却不用,通常是因为 Kafka 原生不支持任意延迟(只能靠多级时间轮 topic 或外部调度),而 RabbitMQ 的延迟插件/死信队列更直接。选型上:Kafka 适合高吞吐、日志/事件流、分区顺序、可回放;RabbitMQ 适合复杂路由、低延迟、逐条确认、延迟/死信场景;RocketMQ 在事务消息、延迟消息、削峰上更贴近业务。回答时先讲业务语义(延迟精度、可靠性、量级、是否需要回放),再讲组件能力,别只背对比表。

MySQL 隔离级别与幻读。 四种隔离级别与对应问题:RU 脏读、RC 不可重复读、RR 幻读(InnoDB 很大程度避免)、Serializable 全避免但性能最差。InnoDB 默认 RR。快照读靠 MVCC:每行有隐藏的 DB_TRX_ID/DB_ROLL_PTR,undo log 串成版本链,ReadView 记录活跃事务集合,读取时沿版本链找到第一个可见版本;RR 在事务首次快照读时生成 ReadView 并复用,所以同一事务多次读一致。当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)靠 Next-Key Lock(记录锁 + 间隙锁)锁住范围,阻止其他事务在间隙插入,从而避免幻读。要注意「RR 下幻读并非绝对不存在」:如果先快照读、另一个事务插入并提交、当前事务再当前读,就可能看到新行——这是隔离级别与锁的边界,面试时点出来是加分项。