汽车之家后端一面:JWT 鉴权、Java 基础与规则引擎
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
项目与鉴权
- 先做一下自我介绍。
- 挑一个你认为比较有代表性的项目/经历,详细介绍一下。
- 你这个项目采用了什么技术框架?主要的技术难点是什么?
- 为什么没有直接使用 Spring Security,而是自己做这套鉴权?
- 你现在这套 Token 方案在安全性上是怎么考虑的?
- 如果让你重新设计这套鉴权方案,你会怎么设计?
- JWT 的底层原理是什么?
- 你们现在密码是怎么存的?
AI Coding
- 你开发过程中主要使用哪些 AI 工具?
- 你平时使用 AI 辅助开发的流程是什么?
- AI 对你的开发效率到底有多大提升?
- 你怎么看 AI 对未来软件开发/岗位分工的影响?
Java 基础
- Java 中 == 和 equals() 有什么区别?
- HashMap 的底层结构是什么?
- JDK 1.8 对 HashMap 做了什么优化?为什么引入红黑树?
- 链表转红黑树的条件是什么?
- 单例模式怎么实现?
- 线程池有哪些核心参数?
- 线程池提交任务后的执行流程是什么?
- 进程和线程有什么区别?
大模型 / RAG
- 为什么大模型会产生幻觉?
- 你会怎么降低或者处理大模型幻觉?
- 了解 RAG 吗?
Drools / 规则引擎
- 你们保险理算用的是什么规则引擎?
- 规则发生变化的时候是怎么处理的?
- 你们支持动态规则配置吗?
- 如果预置模板不支持一种新的业务逻辑怎么办?
- 规则文件本质还是程序写死的,那这种方案的动态能力体现在哪里?
OpenClaw / Agent
- OpenClaw(龙虾)在你们公司实际是怎么应用的?
- 这个东西线上实际使用得多吗?实际效果怎么样?
Redis / 并发
- 你简历里基于 Redis SETNX 做的并发控制,具体逻辑是什么?
- 如果把这个场景换成商品库存,多个人同时抢一个商品,怎么避免超卖?
- 如果基于 Redis NX 来做,你会怎么设计?
延迟队列 / MQ
- 为什么要自己设计 MySQL + Redis 延迟队列?
- 项目已经有 Kafka,为什么不用成熟中间件?
- Kafka 和 RabbitMQ 你会怎么做技术选型?
MySQL
- MySQL 有哪些事务隔离级别?
- MySQL 默认隔离级别是什么?
- 不同隔离级别分别会出现什么问题?
- InnoDB 在 RR 下是怎么解决幻读的?
职场场景
- 如果你刚入职,领导给了一个紧急任务,但你评估自己按期做不完,你怎么办?
- 你会什么时候跟领导沟通?
- 如果你接手一个遗留系统,发现前任留下的方案不合理,但现在有紧急需求要上线,你怎么办?
- 这种情况下你怎么权衡重构和最小改动?
- 你最近在研究什么技术?
《参考解析》
为什么自研鉴权、以及 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 下幻读并非绝对不存在」:如果先快照读、另一个事务插入并提交、当前事务再当前读,就可能看到新行——这是隔离级别与锁的边界,面试时点出来是加分项。