本文只整理技术类面试题。题目尽量模拟面试官根据简历技术点进行追问的方式,少依赖具体业务背景,重点覆盖状态机、幂等、事务、Redis、MQ、缓存一致性、规则引擎、适配器模式、权限认证等常见高频技术点。
回答时可以先讲通用技术方案,再补一句“我在项目里是怎么落地的”。这样既像真实面试表达,也不会让回答显得只是在背业务流程。
供应商管理系统(SRM)
-
你在项目里提到做了状态机,状态流转规则是怎么设计和校验的?
参考答案:状态机一般会定义状态枚举、事件枚举和允许流转关系。接口层不允许直接传目标状态,而是传业务动作,Service 根据当前状态和事件判断是否允许流转。校验通过后再更新状态,并记录状态变更日志。这样可以避免前端篡改状态,也能让状态规则集中维护。
-
状态更新接口如何处理并发提交?只在代码里先查状态再更新可以吗?
参考答案:只先查状态不够,因为查询和更新之间有并发窗口。更稳妥的是使用 CAS 条件更新,把当前状态放进
WHERE条件中,例如WHERE id = ? AND status = ?。如果影响行数为 1,说明更新成功;如果为 0,说明状态已经变化,需要返回已处理或提示刷新。 -
创建关联账号或关联资源时,如何防止重复创建?
参考答案:需要业务层幂等和数据库唯一约束双重保障。业务层先根据业务主键判断是否已经创建过,创建时放在事务里完成主表更新和关联资源创建。数据库层对业务唯一键加唯一索引,即使并发绕过业务判断,也能防止插入重复数据。
-
用 Redis 自增生成业务编码时,如果后续数据库事务失败,Redis 序号要不要回滚?
参考答案:通常不回滚。编码一般要求唯一和趋势递增,不要求绝对连续。回滚 Redis 序号在并发场景下反而容易造成重复编号。正确做法是接受跳号,数据库唯一索引兜底。如果业务强制连续编号,需要使用更复杂的发号服务或数据库事务号段。
-
文件上传接口如何防止用户上传伪造类型或恶意文件?
参考答案:不能只看文件后缀。通常要做扩展名白名单、MIME 类型校验、Magic Bytes 文件头校验、文件大小限制、文件名重命名、路径隔离和私有存储。上传后最好通过对象存储访问,不直接暴露服务器本地路径。
-
XXL-Job 分片任务通常怎么写查询条件?如何避免单个分片一次查太多数据?
参考答案:可以通过
id % shardTotal = shardIndex或按业务 ID 取模进行分片。每个分片内部仍然要分页,比如按id > lastId LIMIT 500游标分页,避免一次加载大量数据。任务处理结果要落日志,失败数据要能重试。 -
多租户系统中,为什么有些接口不能完全依赖自动租户插件?
参考答案:自动租户插件只能解决租户级隔离,但有些接口还需要更细的数据范围控制,比如当前登录人只能看自己负责的数据。如果只依赖自动追加
tenant_id,同一租户内的数据仍可能越权。因此敏感接口要在业务代码中显式追加数据范围条件。 -
业务完成后需要发送通知,为什么不建议把通知调用放在主事务里同步执行?
参考答案:通知属于外围操作,邮件、短信、站内信服务失败不应该导致主业务回滚。更合理的方式是主事务提交后写本地消息表或发送 MQ,由异步消费者发送通知。失败后可以重试,既保证主链路性能,又能提高通知可靠性。
采购管理系统(PMS)
-
复杂单据状态流转如何防止非法跳转?
参考答案:可以用状态机或状态流转白名单控制。每个动作都要校验当前状态是否允许执行,不能直接根据前端传入的目标状态更新。更新时还要使用带当前状态条件的 SQL,避免并发情况下旧状态覆盖新状态。
-
综合评分算法中,不同维度的指标如何统一计算?
参考答案:不同指标量纲不同,需要先归一化。越小越好的指标可以用
最优值 / 当前值 × 100,越大越好的指标可以直接按比例转换成分数。再按权重加权求和。权重应该配置化,方便不同场景调整。 -
Seata AT 模式的执行流程是什么?它靠什么实现回滚?
参考答案:AT 模式一阶段执行本地 SQL,并记录 undo_log,包括修改前后的数据镜像,然后提交本地事务。全局事务成功时删除 undo_log;失败时根据 undo_log 生成反向 SQL 回滚。它适合关系型数据库强一致场景,但要注意全局锁和性能开销。
-
如果不用分布式事务,如何用 MQ 做最终一致?
参考答案:本地事务中先更新本地业务表,并写本地消息表。事务提交后异步发送 MQ。下游消费成功后更新消息状态;失败则依靠 MQ 重试和定时补偿任务。消费者必须做幂等,避免重复消费导致重复更新。
-
定时任务自动生成数据时,幂等键应该如何设计?
参考答案:幂等键要能唯一标识一次业务触发,通常由租户、业务对象、触发类型、日期或周期组成。生成前先查是否已存在处理中或已生成的数据,数据库层再加唯一索引兜底。这样定时任务重复执行也不会生成重复记录。
-
到期提醒类任务如何避免每天重复发送同一条提醒?
参考答案:可以设计提醒记录表,唯一键为
业务ID + 提醒类型 + 提醒日期。发送前先插入提醒记录,插入成功才发送;唯一键冲突说明已经提醒过。发送失败可以记录状态和重试次数,由定时任务补偿。 -
预测类计算为什么要考虑在途数据、周期参数和安全系数?
参考答案:预测不能只看当前数值,还要考虑未来已确定会到达的数据、业务处理周期和安全缓冲。计算时通常会用历史均值或加权均值估算未来需求,再减去当前可用量和在途量,最后加安全系数,避免预测过低导致断档。
-
涉及主表和明细表的查询,索引通常怎么设计?
参考答案:主表通常按租户、状态、时间建组合索引,明细表按租户、业务对象 ID、SKU 或关联 ID 建索引。查询时要让高选择性的条件尽量走索引,并避免在索引字段上使用函数。跨表查询时要关注驱动表和 JOIN 字段是否有索引。
仓储管理系统(WMS)
-
高并发扣减数量时,为什么建议用单条 SQL 原子更新?
参考答案:先查再扣会出现并发窗口。单条条件更新可以把判断和扣减交给数据库原子完成,例如在
WHERE中判断可扣数量是否足够。影响行数为 1 表示成功,为 0 表示数量不足或数据已被其他事务修改。 -
为什么要把“总量、冻结量、异常量”拆成不同字段?
参考答案:拆字段可以表达不同业务状态。总量代表实物数量,冻结量代表已被占用但未完成处理的数量,异常量代表不可用数量。可用量通常由这些字段计算出来。这样比直接改一个库存字段更清晰,也方便处理取消、释放、异常隔离等场景。
-
流水表为什么不能物理修改?如果写错了怎么办?
参考答案:流水表承担审计和追溯职责,修改历史会破坏证据链。写错时应该新增一条冲正流水或调整流水,关联原流水 ID,说明修正原因。这样既能修正最终数据,又能保留错误和修正过程。
-
如何根据流水表还原历史快照?全量扫描性能差怎么办?
参考答案:可以从初始值累加到目标时间,也可以从当前值倒推。数据量大时应定期生成快照表,例如每日快照。查询时从最近快照开始,只累加快照之后的流水,避免每次扫描全部历史流水。
-
先进先出和近效期优先这类分配策略,技术上如何实现?
参考答案:先过滤可用数据,例如未锁定、未过期、数量大于 0,再按入库时间或过期时间排序。分配时按排序结果逐条占用,直到满足需求数量。为了避免并发冲突,占用时仍要用条件更新或事务控制。
-
盘点或批量调整时,如何避免和并发出库冲突?
参考答案:可以先锁定操作范围,例如库位、批次或库存行,出库分配时过滤锁定数据。盘点调整时只调整未冻结、可盘点的部分。状态更新要带条件,避免重复锁定或释放。关键调整操作要放在事务中。
-
消费 MQ 消息时,如何避免重复扣减或重复生成单据?
参考答案:消费者要基于业务唯一键做幂等,比如
业务单号 + 事件类型。可以先查是否已处理,也可以在处理记录表上加唯一索引。收到重复消息时直接返回成功,不再执行扣减或创建逻辑。 -
高频查询数量类数据时,数据库索引和缓存分别怎么用?
参考答案:数据库层要根据查询条件设计组合索引,比如租户、仓库、SKU、状态。缓存适合读多写少或热点读场景,但数量类数据更新频繁,要注意一致性。核心写操作仍以数据库为准,缓存可以用延迟双删、更新后删除或短 TTL 降低脏读风险。
商品订单管理系统(PIM + OMS)
-
根据多个规格维度生成组合数据时,如何避免重复组合?
参考答案:可以为每个组合生成稳定签名,比如将规格键值排序后序列化,再计算 hash。数据库对
主体ID + 组合签名建唯一索引。即使接口重复提交,也只能插入一次。 -
Webhook 接口如何做签名校验和防重放?
参考答案:签名可以用 HMAC-SHA256,把请求体、时间戳、nonce 一起参与计算。服务端先校验时间窗口,再校验签名,最后把 nonce 存 Redis,重复 nonce 直接拒绝。这样既防篡改,也防止合法请求被重复发送。
-
分布式锁的 key、value、TTL 和释放逻辑分别要注意什么?
参考答案:key 要能唯一标识业务操作,value 要用请求唯一 ID,TTL 要覆盖正常处理时间并避免死锁。释放锁时必须判断 value 是否匹配,不能直接删除 key,否则可能误删其他线程新加的锁。数据库唯一索引仍然要作为兜底。
-
Redis 扣减类操作为什么适合用 Lua 脚本?
参考答案:Lua 可以把“判断是否足够”和“扣减”放在 Redis 单线程中原子执行,避免
GET + DECR的并发窗口。库存不足时直接返回失败,不会先扣成负数再补偿。复杂场景还可以一次校验多个 key 并一起扣减。 -
Spring State Machine 中 Guard 和 Action 应该如何分工?
参考答案:Guard 负责判断是否允许流转,不能产生副作用;Action 负责流转时执行的业务动作,比如写日志、发消息、更新关联数据。这样可以让状态判断和业务动作解耦,状态机规则也更容易测试。
-
规则引擎如何设计才能符合开闭原则?
参考答案:可以抽象统一规则接口,每个规则独立实现,并通过 Spring 自动注入规则列表。规则的启停、权重、阈值放到配置表中。新增规则只新增实现类和配置,不改主流程代码。
-
AT 分布式事务里 undo_log 失效或回滚失败时怎么处理?
参考答案:首先要监控全局事务状态和回滚失败日志。回滚失败可能是数据被其他事务修改、表结构不符合要求或 undo_log 异常。处理方式包括自动重试、告警、人工补偿和对关键表加约束。业务上也要避免长事务和热点行竞争。
-
按时间分表后,分页查询和按 ID 查询分别怎么路由?
参考答案:按 ID 查询最好能从 ID 或业务单号解析出时间片,直接路由到对应分表。分页查询如果跨时间范围,需要查询多张表后合并排序。为了提高效率,可以限制查询时间范围,或者维护全局索引表辅助定位。
物流管理系统(TMS)
-
规则过滤和评分排序为什么要拆成两个阶段?
参考答案:过滤阶段用于排除不满足硬条件的数据,比如状态不可用、范围不匹配、限制不满足。排序阶段只对候选数据计算综合评分。拆开后 SQL 可以先利用索引缩小范围,复杂评分放到业务层处理,性能和可维护性都更好。
-
Spring 注入
Map<String, Interface>时,Map 的 key 是什么?如何避免拿不到实现类?参考答案:默认 key 是 BeanName,不一定等于业务编码。如果要按业务编码获取实现类,可以显式指定 BeanName,或者每个实现类提供
supportCode()方法,启动后手动构建code -> adapter映射。否则容易出现编码和 BeanName 不一致导致查不到适配器。 -
调用第三方接口时,如何设计本地幂等和对方幂等?
参考答案:本地幂等要用业务唯一键、分布式锁、状态校验和唯一索引,保证本系统不会重复发起同一业务操作。对方幂等取决于第三方能力,如果支持客户请求号或查询接口,就用请求号查询补偿;如果不支持,就要降低自动重试次数,并把异常交给人工处理。
-
第三方状态码很多且不统一,如何映射成本系统标准状态?
参考答案:要建立状态映射层,把第三方原始状态码映射成系统统一枚举。原始状态保留在明细表中,主表只保存标准状态。映射规则可以配置化,新增第三方时只新增映射规则,不影响上层业务判断。
-
分片任务中如何同时做到并发处理和限流保护?
参考答案:先按分片取数据,再按第三方或渠道分组。不同分组可以并行,同一分组内部用线程池大小、令牌桶或信号量控制并发。这样能提高总体吞吐,又不会对单个第三方接口打出过高 QPS。
-
熔断打开后,接口应该直接失败还是做降级?
参考答案:要看接口类型。查询类接口可以返回缓存或最近一次结果;试算类接口可以用本地配置估算;强依赖写接口可以提示稍后重试或切换备用通道。熔断的目的是保护系统和下游,不是简单抛异常。
-
本地消息表一般有哪些状态?状态流转时机是什么?
参考答案:常见状态有 INIT、SENDING、SENT、FAILED、DEAD。业务事务内写 INIT;发送前改 SENDING;发送成功改 SENT;发送失败改 FAILED 并增加重试次数;超过最大重试次数改 DEAD 并告警。定时任务负责扫描 INIT/FAILED 继续发送。
-
大文件导入如何避免内存溢出?批量入库时如何处理失败行?
参考答案:读取 Excel 或 CSV 时要流式读取,不能一次加载全部数据。每读取一定数量就批量入库并清空内存缓冲。失败行要记录行号、原始数据和错误原因,不能因为一行失败导致整个文件不可追溯。必要时支持失败数据重新导入。
权限安全系统
-
随机 Token + Redis 模式下,一次请求的认证链路是什么?
参考答案:前端携带 Token,请求进入后拦截器提取 Token,去 Redis 查询 Token 对应的登录态和用户 ID。查不到说明未登录或已过期;查到后把用户信息放到上下文中,后续权限校验和数据隔离都从上下文取当前用户和租户信息。
-
权限列表是每次都查数据库,还是登录时查一次?如何优化?
参考答案:不能每次都查数据库,压力太大。通常在第一次鉴权或登录后加载权限列表,并缓存到 Redis。后续鉴权先查缓存,缓存没有再查库。角色或权限变更时删除相关用户缓存,保证变更能生效。
-
权限缓存的 key 和失效策略如何设计?
参考答案:key 可以包含租户和用户 ID,例如
auth:perm:{tenantId}:{userId}。角色权限变更后,要找到受影响用户并删除缓存,或者使用角色版本号机制,让用户下次请求发现版本变化后重新加载。敏感权限变更还可以强制下线。 -
多租户登录时,为什么不能只根据用户名查询用户?
参考答案:不同租户可能有相同用户名。登录时必须先根据租户标识定位租户,再在该租户下查询用户并校验密码。登录成功后会话中保存租户 ID,后续所有查询都基于租户上下文进行隔离。
-
认证、授权、数据权限分别适合放在哪一层做?
参考答案:认证适合在 Filter 或 Interceptor 提前完成,判断 Token 是否有效;接口授权适合在 Interceptor 或注解切面中完成,判断是否有权限码;数据权限适合在 Service 或 SQL 构造阶段完成,因为它依赖具体业务数据范围。三者职责不同,不能混在一起。
-
动态拼接数据权限 SQL 时,如何避免 SQL 注入?
参考答案:数据范围必须由后端根据当前用户角色生成,不能信任前端传入。可参数化的值使用 MyBatis 占位符;不能参数化的字段名、排序字段必须走白名单映射。任何直接拼接用户输入的 SQL 都有注入风险。
-
为什么密码存储推荐 BCrypt,而不是 MD5 或 SHA?
参考答案:MD5 和 SHA 计算速度快,容易被暴力破解。BCrypt 自带随机盐,并且可以配置计算成本,让破解成本变高。同一个密码每次生成的哈希也不同,能防止彩虹表攻击。系统只保存哈希,不保存明文密码。
-
踢人下线、多端登录控制和 Token 续期分别如何实现?
参考答案:踢人下线就是删除 Redis 中指定 Token 或用户会话;多端控制可以维护用户 Token 列表,超过数量时踢掉旧 Token 或拒绝新登录;Token 续期可以用 RefreshToken 换新的 AccessToken,同时校验用户状态、租户状态和会话是否仍有效。