Series Article

Day02 · SRM 核心业务面试准备

一、核心业务流程(面试必讲)

1. 供应商审核流程

流程概述:

创建草稿 → 上传资质 → 提交审核 → 负责人审核(通过/拒绝/退回) → 创建Portal账号 → 正式上线

关键技术点:

  • 状态机白名单控制状态流转(5种状态,7种合法流转)
  • CAS乐观锁保证并发安全:WHERE status = 草稿
  • 审核日志只增不改,完整记录审核历史
  • Portal账号创建时检查邮箱唯一性

面试时怎么讲: “采购专员创建供应商草稿,上传营业执照等资质文件,提交审核。系统用状态机校验状态流转是否合法,用CAS乐观锁更新状态,防止并发修改。采购负责人审核通过后,系统自动创建Portal账号并发送欢迎邮件。所有操作写入审核日志,可追溯。“


2. 供应商绩效评分流程

评分维度(满分100分):

  • 准时交货率(25分)= 准时到货数 / 总订单数 × 25
  • 质量合格率(25分)= 合格批次数 / 总批次数 × 25
  • 响应速度(25分):≤2小时=25分,2-8小时=20分…
  • 价格竞争力(25分):价格系数<0.90=25分…

评级标准:

  • S级(≥90分):优质供应商,增加合作份额
  • A级(75-89分):良好供应商,正常合作
  • B级(60-74分):待改进,发送改进通知
  • C级(<60分):预警,风险提示

技术实现:

  • XXL-Job定时任务:每月1号00:30执行
  • 任务分片:按供应商ID取模分片,多执行器并行计算
  • 幂等性保证:唯一索引(supplier_id, score_month)
  • 评分记录只增不改,可追溯历史变化

面试时怎么讲: “定时任务每月1号凌晨执行,查询上月采购数据,计算四个维度评分,汇总后得出综合评级。任务支持分片,多个执行器并行计算。评分记录写入流水表,只增不改。如果评级变化,自动发送通知,降为C级触发风险预警。“


二、核心技术方案(简历亮点)

1. 状态机设计

为什么需要:

  • 防止非法状态流转(如草稿直接跳到已停用)
  • 集中管理状态流转逻辑
  • 便于维护和扩展

实现方式:

// 状态流转白名单
private static final Set<Transition> ALLOWED_TRANSITIONS = Set.of(
    transition(DRAFT, PENDING_AUDIT),      // 草稿 → 待审核
    transition(PENDING_AUDIT, APPROVED),   // 待审核 → 已通过
    transition(PENDING_AUDIT, REJECTED),   // 待审核 → 已拒绝
    // ... 其他合法流转
);

// 状态变更前校验
if (!SupplierStateMachine.canTransit(currentStatus, targetStatus)) {
    throw new BusinessException("当前状态不允许此操作");
}

// CAS乐观锁更新
int rows = supplierMapper.update(null, 
    new LambdaUpdateWrapper<Supplier>()
        .eq(Supplier::getId, supplierId)
        .eq(Supplier::getStatus, currentStatus)  // 关键:WHERE条件
        .set(Supplier::getStatus, targetStatus)
);

简历怎么写: “设计状态机管理供应商生命周期,定义5种状态和7种合法流转路径。用状态机白名单校验流转合法性,用CAS乐观锁防止并发修改,所有状态变更写入审核日志。避免了非法状态流转,保证了数据一致性。“


2. 文件上传方案(OSS)

技术选型:

  • 开发环境:本地存储
  • 生产环境:阿里云OSS

核心实现:

// 1. 文件校验
- 类型白名单:PDF/JPG/PNG
- 大小限制:最大10MB
- 文件名校验:防止路径穿越攻击

// 2. 存储路径设计
supplier/cert/2025/01/17/uuid.pdf  // 按日期分目录

// 3. 访问权限控制
- 生成临时URL(有效期1小时)
- 租户隔离:只能访问本租户文件

// 4. 有效期管理
- 定时任务每天凌晨检查30天内到期的资质
- 自动发送到期提醒

简历怎么写: “实现文件上传模块,采用本地+OSS双模式。上传时校验文件类型和大小,生成UUID文件名,按日期分目录存储。文件访问通过接口控制权限,生成临时URL。定时任务每天检查资质有效期,提前30天发送到期提醒。“


3. 定时任务方案(XXL-Job)

为什么选XXL-Job:

  • 分布式调度,支持任务分片
  • 可视化管理界面
  • 失败自动重试
  • 完整的执行日志

核心任务:供应商绩效评分

@XxlJob("supplierScoreCalculate")
public void calculateScore() {
    // 1. 获取分片参数
    int shardIndex = XxlJobHelper.getShardIndex();
    int shardTotal = XxlJobHelper.getShardTotal();
    
    // 2. 查询本分片的供应商(按ID取模)
    List<Supplier> suppliers = supplierMapper.selectList(
        new LambdaQueryWrapper<Supplier>()
            .eq(Supplier::getStatus, APPROVED)
            .apply("MOD(id, {0}) = {1}", shardTotal, shardIndex)
    );
    
    // 3. 遍历计算评分
    for (Supplier supplier : suppliers) {
        calculateSupplierScore(supplier.getId(), lastMonth);
    }
}

任务幂等性:

  • 唯一索引:UNIQUE KEY (supplier_id, score_month)
  • 重复执行会跳过已计算的记录

简历怎么写: “用XXL-Job实现供应商绩效评分定时任务,每月1号凌晨执行。任务支持分片,按供应商ID取模分配到多个执行器并行计算。查询上月采购数据,计算四维度评分,写入流水表。用唯一索引保证幂等性,评级变化自动发送通知。“


4. 审核幂等性设计

为什么需要幂等性:

  • 网络超时导致前端重复提交
  • 用户多次点击按钮
  • 系统重试机制

实现方案:

// 方案1:状态前置判断(不是真正的幂等)
if (supplier.getStatus() == APPROVED) {
    return Result.success("该供应商已审核通过");
}

// 方案2:CAS乐观锁(推荐)
int rows = supplierMapper.update(null, 
    new LambdaUpdateWrapper<Supplier>()
        .eq(Supplier::getId, supplierId)
        .eq(Supplier::getStatus, PENDING_AUDIT)  // 关键
        .set(Supplier::getStatus, APPROVED)
);
if (rows == 0) {
    throw new BusinessException("供应商状态已变化");
}

// 方案3:唯一约束兜底
- portal_user_id唯一索引
- 邮箱唯一索引

简历怎么写: “审核操作用CAS乐观锁保证幂等性,UPDATE时WHERE条件判断当前状态必须是待审核。Portal账号创建时检查邮箱唯一性,防止重复创建。即使网络超时导致重复提交,也不会产生脏数据。“


三、简历怎么写

项目描述

项目:跨境电商 SaaS 供应链管理平台 - 供应商管理模块(SRM)
时间:2024.06 - 2024.12
角色:核心开发
技术:Spring Boot 3.2、MyBatis-Plus、MySQL 8.0、Redis、XXL-Job、阿里云OSS

这是个跨境电商的供应链 SaaS 平台,我负责供应商管理模块。系统管理供应商全生命周期,
包括信息管理、审核流程、资质文件管理、绩效评分、Portal协同等。支持多租户,
日均处理审核单 200+,管理供应商 5000+。

核心难点:
1. 供应商审核流程的状态流转控制
2. 审核操作的幂等性保证
3. 文件上传和存储方案
4. 供应商绩效自动评分和分级
5. 定时任务的分片和幂等性

核心亮点

1. 状态机管理供应商生命周期

问题:供应商状态可以随意修改,无法控制状态流转的合法性

方案:设计状态机白名单,集中管理状态流转逻辑

具体实现:

  • 定义5种状态:草稿、待审核、已通过、已拒绝、已停用
  • 定义7种合法流转路径,用Set存储状态流转白名单
  • 每次状态变更前,先用状态机校验是否合法
  • 用CAS乐观锁更新状态,WHERE条件判断当前状态
  • 所有状态变更写入审核日志,只增不改不删

效果:

  • 完全避免了非法状态流转
  • 状态流转逻辑集中管理,易于维护
  • 审核历史完整可追溯

2. 文件上传和OSS存储方案

问题:供应商资质文件需要长期保存,本地存储不可靠

方案:采用本地+OSS双模式,生产环境用阿里云OSS

具体实现:

  • 文件校验:类型白名单(PDF/JPG/PNG)、大小限制(10MB)
  • 文件名:UUID防止重名覆盖
  • 存储路径:按日期分目录(supplier/cert/2025/01/17/uuid.pdf)
  • 访问控制:生成临时URL(有效期1小时),租户隔离
  • 有效期管理:定时任务每天检查30天内到期的资质,自动提醒

效果:

  • 文件存储可靠性99.9999999%
  • 支持CDN加速,全球访问快
  • 资质到期提前提醒,避免过期

3. 供应商绩效自动评分系统

问题:人工评估供应商质量,主观性强,无法量化对比

方案:设计四维度评分体系,定时任务自动计算

具体实现:

  • 评分维度:准时交货率、质量合格率、响应速度、价格竞争力,各25分
  • 定时任务:XXL-Job每月1号00:30执行
  • 任务分片:按供应商ID取模分片,多执行器并行计算
  • 评分计算:查询上月采购数据,计算各维度评分,汇总得出综合评级(S/A/B/C)
  • 幂等性保证:唯一索引(supplier_id, score_month)
  • 评分记录只增不改,可追溯历史变化

效果:

  • 评分客观公平,数据驱动决策
  • 评级变化自动通知,及时预警
  • 支持10000+供应商并行计算,2分钟完成

4. 审核操作的幂等性设计

问题:网络超时导致前端重复提交,可能产生重复审核、重复创建Portal账号

方案:CAS乐观锁 + 唯一约束兜底

具体实现:

  • CAS乐观锁:UPDATE时WHERE条件判断当前状态必须是待审核
  • 状态前置判断:先查询当前状态,如果已审核通过直接返回成功
  • 唯一约束:portal_user_id、邮箱建唯一索引,防止重复创建
  • 审核日志:记录每次操作,可追溯

效果:

  • 完全避免了重复审核和重复创建账号
  • 即使网络超时重复提交,也不会产生脏数据

四、面试高频问题

Q1:状态机是什么?为什么要用状态机?

答:状态机描述一个对象在生命周期中可能处于哪些状态,以及状态之间如何流转。

我们用状态机管理供应商,定义了5种状态和7种合法流转路径。好处是:

  1. 防止非法状态流转(如草稿直接跳到已停用)
  2. 状态流转逻辑集中管理,易于维护
  3. 便于扩展新的状态和流转规则

实现上,我们用一个Set存储状态流转白名单,每次状态变更前先校验是否在白名单中。

Q2:如何保证审核操作的幂等性?

答:我们用三层保障:

  1. 状态前置判断:先查询当前状态,如果已审核通过,直接返回成功
  2. CAS乐观锁:UPDATE时WHERE条件判断当前状态必须是待审核,如果状态已变化,更新失败
  3. 唯一约束兜底:Portal账号的邮箱建唯一索引,防止并发创建重复账号

这样即使网络超时导致重复提交,也不会产生脏数据。

Q3:文件上传为什么用OSS而不是本地存储?

答:本地存储有几个问题:

  1. 占用服务器磁盘空间
  2. 服务器重启或迁移,文件可能丢失
  3. 无法做CDN加速,下载慢
  4. 难以扩展

OSS的优势:

  1. 海量存储,按需付费
  2. 高可用,99.9999999%数据可靠性
  3. 支持CDN加速,全球访问快
  4. 自动备份,不怕丢失

我们采用本地+OSS双模式,开发环境用本地存储方便调试,生产环境用OSS保证可靠性。

Q4:定时任务如何保证幂等性?

答:我们用唯一索引保证幂等性。

在supplier_score_log表上建唯一索引:UNIQUE KEY (supplier_id, score_month)

如果重复执行,插入时会报DuplicateKeyException,捕获异常后跳过。这样即使任务重试,也不会重复计算评分。

Q5:任务分片是怎么实现的?

答:XXL-Job支持任务分片,原理是按数据ID取模分配。

比如有5个执行器,10000个供应商:

  • 执行器1:处理ID % 5 = 0的供应商(2000个)
  • 执行器2:处理ID % 5 = 1的供应商(2000个)

代码实现:

int shardIndex = XxlJobHelper.getShardIndex();  // 当前分片序号
int shardTotal = XxlJobHelper.getShardTotal();  // 总分片数

List<Supplier> suppliers = supplierMapper.selectList(
    new LambdaQueryWrapper<Supplier>()
        .apply("MOD(id, {0}) = {1}", shardTotal, shardIndex)
);

这样多个执行器并行计算,性能提升5倍。

Q6:如果供应商本月没有采购数据,评分怎么处理?

答:不更新评分,保留上月评分,在calc_remark字段注明”本月无采购数据”。

这样既保证了评分的连续性,又记录了特殊情况。如果连续3个月没有采购数据,可以考虑标记为”不活跃供应商”。

Q7:状态机白名单如果要新增流转路径,怎么扩展?

答:直接在ALLOWED_TRANSITIONS集合中添加新的流转即可:

private static final Set<Transition> ALLOWED_TRANSITIONS = Set.of(
    // 原有流转
    transition(DRAFT, PENDING_AUDIT),
    // 新增流转
    transition(APPROVED, PENDING_AUDIT)  // 已通过 → 待审核(重新审核)
);

这样状态流转逻辑集中管理,扩展非常方便。

Q8:文件上传如何防止恶意攻击?

答:我们有多层防护:

  1. 文件类型白名单:只允许PDF/JPG/PNG
  2. 文件大小限制:最大10MB
  3. 文件名校验:防止路径穿越攻击(../ 或 \)
  4. 访问权限控制:租户隔离,只能访问本租户文件
  5. 临时URL:有效期1小时,过期自动失效
  6. 病毒扫描:上传后自动扫描(可选)

五、面试准备建议

必须能画的图

供应商审核流程:

创建草稿 → 上传资质 → 提交审核 → 负责人审核

                        通过 → 创建Portal账号 → 正式上线
                        拒绝 → 通知采购专员
                        退回 → 返回草稿状态

状态机流转图:

草稿(0) → 待审核(1) → 已通过(2) → 已停用(4)
            ↓            ↑
         已拒绝(3) ←──────┘

必须能讲清楚的

  • 什么是状态机?为什么要用状态机?
  • 如何保证审核操作的幂等性?
  • 文件上传为什么用OSS?
  • 定时任务如何保证幂等性?
  • 任务分片是怎么实现的?

表达技巧

  1. 先讲业务,再讲技术
  2. 用数据说话:性能提升5倍、可靠性99.9999999%
  3. 主动展开:不要等面试官问,主动讲状态机、幂等性、分片
  4. 准备追问:每个点都要准备2-3个追问
  5. 不要背书:要自然地表达

最后提醒:

面试前把这份笔记看2-3遍,重点记住:

  • 供应商审核流程(5个步骤)
  • 供应商绩效评分流程(4个维度)
  • 状态机设计(5种状态,7种流转)
  • 文件上传方案(OSS)
  • 定时任务方案(XXL-Job分片)
  • 简历上的4个亮点

面试时不要背书,要像讲故事一样自然地表达。如果面试官追问细节,可以展开讲技术实现。准备好画图,审核流程图、状态机流转图一定要能画出来。