You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

13 KiB

n04-onboard 设计

Facts and Constraints

  • 当前后端为 Java 17、Spring Boot 3.5.16、Spring MVC、Spring Security、Spring Data JPA、Flyway、MySQL 8.4,测试使用 JUnit、Mockito、MockMvc 和 H2 MODE=MySQL。
  • 现有迁移已到 V37。本模块使用 V38 建表、V39 写入通用清单种子,不修改既有迁移或既有表结构。
  • companies.owner_user_id 唯一,现有 CompanyService 已提供 COMPANY 角色与所有权校验模式。
  • job_reviews 关联 job_id,企业评价必须通过 jobs.company_id 关联;公开评价限定为 status=PUBLISHED 且 moderation_status IN (AUTO_APPROVED, APPROVED)。
  • 现有八维评价维度由 ReviewDimension 枚举统一维护;公司知识摘要复用该枚举,不新增维度定义。
  • 统一响应使用 ApiResponse,错误使用 BusinessException 与 ErrorCode,请求 ID 从 X-Request-Id 读取。
  • 产品要求企业未配置时回退通用清单,公司信息不足时回退企业基础信息,且不调用 AI。

Goals

  • 用最少的数据表和接口完成学生清单、个人完成状态、企业清单配置、公司知识配置与公开评价摘要。
  • 保持角色、企业所有权、响应字段和错误码可测试。
  • 保持创建幂等、更新并发可控、软删除可重复执行。
  • 不改变企业、岗位、Offer、评价等既有接口语义。

Non-Goals

  • 不实现 ADMIN 模板治理、审核发布、版本回滚和 CMS。
  • 不实现通知、内容推荐、试用期提醒、入职回访、在职反馈。
  • 不按岗位、投递或 Offer 生成个性化内容。
  • 不存储或渲染 HTML/富文本,不接入 AI 生成或 AI 审核。
  • 不实现前端页面和前端缓存策略。

Architecture

新增包 com.mydreamjob.onboarding:

onboarding/
├─ OnboardingChecklistController.java
├─ CompanyKnowledgeController.java
├─ OnboardingChecklistService.java
├─ CompanyKnowledgeService.java
├─ OnboardingChecklistItem.java
├─ StudentOnboardingProgress.java
├─ CompanyKnowledge.java
├─ OnboardingChecklistItemRepository.java
├─ StudentOnboardingProgressRepository.java
├─ CompanyKnowledgeRepository.java
└─ dto/
   ├─ ChecklistItemView.java
   ├─ EffectiveChecklistResponse.java
   ├─ ProgressUpdateRequest.java
   ├─ ProgressUpdateResponse.java
   ├─ CompanyChecklistItemCreateRequest.java
   ├─ CompanyChecklistItemUpdateRequest.java
   ├─ CompanyChecklistItemView.java
   ├─ CompanyKnowledgeUpdateRequest.java
   ├─ CompanyKnowledgeView.java
   ├─ CompanyKnowledgeOverviewResponse.java
   └─ ReviewSummaryView.java

SecurityConfig 将 /onboarding/** 设为认证访问;Controller/Service 再按角色和所有权执行细粒度校验。所有接口返回 ApiResponse<T>。

API Contract

所有路径均位于 /api/v1 之下。

Method Path Role Request Success Response
GET /onboarding/companies/{companyId}/checklist STUDENT path companyId EffectiveChecklistResponse
PUT /onboarding/companies/{companyId}/checklist/items/{itemId}/completion STUDENT ProgressUpdateRequest ProgressUpdateResponse
GET /onboarding/companies/{companyId}/knowledge STUDENT path companyId CompanyKnowledgeOverviewResponse
GET /onboarding/company/checklist-items COMPANY 无 List<CompanyChecklistItemView>
POST /onboarding/company/checklist-items COMPANY CompanyChecklistItemCreateRequest CompanyChecklistItemView
PUT /onboarding/company/checklist-items/{itemId} COMPANY CompanyChecklistItemUpdateRequest CompanyChecklistItemView
DELETE /onboarding/company/checklist-items/{itemId}?version={version} COMPANY query version CompanyChecklistItemView,active=false
GET /onboarding/company/knowledge COMPANY 无 CompanyKnowledgeView
PUT /onboarding/company/knowledge COMPANY CompanyKnowledgeUpdateRequest CompanyKnowledgeView

Effective Checklist Response

{
  "companyId": 2,
  "companyName": "本源科技",
  "customized": true,
  "totalItems": 13,
  "completedItems": 4,
  "completionRate": 30.8,
  "items": [
    {
      "itemId": 11,
      "source": "COMPANY",
      "itemKey": "company-2-uuid",
      "title": "提交导师联系方式",
      "description": "入职前由 HR 补充",
      "category": "ADMIN",
      "required": false,
      "sortOrder": 200,
      "active": true,
      "completed": false,
      "completedAt": null,
      "version": 0
    }
  ]
}

通用项使用 source=STANDARD;企业项使用 source=COMPANY。完成率保留一位小数,最大为 100.0;清单为空时完成率为 0.0。

Completion Request

{
  "completed": true,
  "version": 0
}
  • 无完成记录时 version 可省略或为 0。
  • 已有记录且请求值不同,必须提供当前 version;版本不一致返回 409 ONBOARDING_PROGRESS_CONCURRENT_UPDATE。
  • 已有记录且请求值与当前值相同,按幂等成功返回,不因旧版本报错。

Company Item Create Request

{
  "title": "提交导师联系方式",
  "description": "入职前由 HR 补充",
  "category": "ADMIN",
  "sortOrder": 200,
  "required": false,
  "idempotencyKey": "client-generated-key"
}
  • title 必填,长度 1-120。
  • description 可空,最大 1000。
  • category 仅允许 DOCUMENT、ADMIN、EQUIPMENT、TRAINING、LOGISTICS、OTHER。
  • sortOrder 范围 0-9999,默认 1000。
  • required 默认 false。
  • idempotencyKey 可空,最大 64;服务端保存规范化请求体 SHA-256,用于同 key 不同内容冲突检测。

Company Item Update/Delete

更新请求包含 version;删除请求通过 version 查询参数传入。两者版本不匹配返回 409 ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE。删除采用软删除,重复删除按成功返回。

Company Knowledge Request

{
  "culture": "重视透明沟通和学习",
  "workPolicies": "弹性工作制;每周三远程",
  "onboardingGuide": "9:30 到 A 座 8 层前台报到",
  "version": 0
}
  • 三个文本字段可空,但至少一个非空。
  • culture 最大 2000,workPolicies 最大 4000,onboardingGuide 最大 4000。
  • 首次创建不要求版本;已有记录必须提供当前版本,冲突返回 409 ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE。

Student Company Knowledge Response

{
  "company": {
    "id": 2,
    "name": "本源科技",
    "logoUrl": null,
    "description": "企业简介",
    "industry": "互联网",
    "website": "https://example.com",
    "address": "上海市",
    "verified": true
  },
  "knowledgeState": "CONFIGURED",
  "knowledge": {
    "culture": "重视透明沟通和学习",
    "workPolicies": "弹性工作制;每周三远程",
    "onboardingGuide": "9:30 到 A 座 8 层前台报到",
    "updatedAt": "2026-09-16T07:00:00"
  },
  "reviewSummary": {
    "state": "SUFFICIENT",
    "totalReviews": 12,
    "averageRating": 4.4,
    "ratingDistribution": {
      "rating1": 0,
      "rating2": 1,
      "rating3": 2,
      "rating4": 4,
      "rating5": 5
    },
    "topTags": ["成长空间", "团队氛围"],
    "dimensionSummary": {
      "TEAM_ATMOSPHERE": {
        "averageScore": 4.5,
        "sampleCount": 8,
        "dataState": "SUFFICIENT"
      }
    }
  }
}

无知识记录时 knowledgeState=BASIC_ONLY、knowledge=null,企业基础信息仍返回。评价少于 3 条时 reviewSummary.state=INSUFFICIENT 或 EMPTY,且 averageRating、ratingDistribution、topTags、dimensionSummary 为空。

Data Model

onboarding_checklist_items

id BIGINT PK
scope_type VARCHAR(20) NOT NULL       -- STANDARD / COMPANY
scope_id BIGINT NOT NULL              -- 0 或 company_id
company_id BIGINT NULL                -- 企业项外键;通用项为 NULL
item_key VARCHAR(80) NOT NULL
title VARCHAR(120) NOT NULL
description VARCHAR(1000) NULL
category VARCHAR(30) NOT NULL
sort_order INT NOT NULL DEFAULT 1000
required BOOLEAN NOT NULL DEFAULT FALSE
active BOOLEAN NOT NULL DEFAULT TRUE
idempotency_key VARCHAR(64) NULL
request_hash CHAR(64) NULL
version BIGINT NOT NULL DEFAULT 0
created_at DATETIME NOT NULL
updated_at DATETIME NOT NULL
UNIQUE (scope_type, scope_id, item_key)
UNIQUE (company_id, idempotency_key)
INDEX (scope_type, scope_id, active, sort_order, id)

scope_type/scope_id 为公司项唯一性提供稳定语义,同时允许通用项使用 scope_id=0;company_id 仅对企业项建立外键,标准项为 NULL。应用层检查 scope_type=COMPANY 时 scope_id=company_id 且 company_id 非空。

student_onboarding_progress

id BIGINT PK
student_user_id BIGINT NOT NULL
item_id BIGINT NOT NULL
completed BOOLEAN NOT NULL
completed_at DATETIME NULL
version BIGINT NOT NULL DEFAULT 0
created_at DATETIME NOT NULL
updated_at DATETIME NOT NULL
UNIQUE (student_user_id, item_id)
FOREIGN KEY student_user_id -> users(id)
FOREIGN KEY item_id -> onboarding_checklist_items(id)

完成状态采用绝对值 Upsert;同一学生与条目唯一。completedAt 在首次置为完成时写入,取消完成时清空。

company_knowledge

id BIGINT PK
company_id BIGINT NOT NULL
culture VARCHAR(2000) NULL
work_policies VARCHAR(4000) NULL
onboarding_guide VARCHAR(4000) NULL
version BIGINT NOT NULL DEFAULT 0
created_at DATETIME NOT NULL
updated_at DATETIME NOT NULL
UNIQUE (company_id)
FOREIGN KEY company_id -> companies(id)

Decisions

Decision 1: 使用统一清单表并保留标准/企业来源

  • Choice:onboarding_checklist_items 使用 scope_type 区分 STANDARD 与 COMPANY;有效清单查询返回两类记录。
  • Rationale:学生只需要一个稳定 itemId 更新完成状态,统一表避免两套进度表、两套排序和重复响应逻辑。
  • Alternatives:标准表与企业表分离;企业配置 JSON 存储在一个字段中。
  • Consequences:需要额外维护 scope 一致性检查;换来了统一的完成状态和软删除语义。

Decision 2: 通用清单固定种子,企业项只做增量

  • Choice:V39 写入通用清单;企业自定义项只追加,不允许覆盖或替换通用项。
  • Rationale:产品明确要求企业未配置时显示通用清单,固定基线可保证所有企业至少有可用内容。
  • Alternatives:允许企业禁用通用项;允许企业完全替换模板。
  • Consequences:MVP 不支持企业裁剪通用项;后续若需要,需新增停用映射而不是修改现有响应语义。

Decision 3: 创建幂等与更新乐观锁分离

  • Choice:创建使用 idempotencyKey + request_hash;更新、删除和知识内容使用 @Version 乐观锁。
  • Rationale:创建需要处理网络重试,更新需要避免管理员或企业多端并发覆盖。
  • Alternatives:所有写接口只依赖数据库唯一键;所有接口都使用请求幂等键。
  • Consequences:客户端创建时需保存幂等键;更新时必须先取得当前版本。

Decision 4: 公司知识摘要只使用公开评价并执行小样本保护

  • Choice:只聚合 PUBLISHED 且审核为 AUTO_APPROVED/APPROVED 的评价;少于 3 条不返回均值。
  • Rationale:与现有公开评价语义一致,降低小样本隐私和误导风险。
  • Alternatives:聚合全部评价;无阈值返回所有均值。
  • Consequences:新企业或评价较少企业只能看到基础资料与评价数量,不显示汇总分数。

Decision 5: 学生访问不绑定 Offer

  • Choice:任何 STUDENT 可按公开存在的 companyId 读取清单和知识,完成状态只属于当前学生。
  • Rationale:产品没有要求入职资格校验,企业基础信息和通用清单也不属于敏感业务数据;此设计避免跨模块状态耦合。
  • Alternatives:必须存在 ACCEPTED Offer;必须存在投递记录。
  • Consequences:后续若要增加资格门槛,需新增权限规则和兼容测试,不改变现有路径。

Error Codes

新增 97xx:

9701 ONBOARDING_CHECKLIST_ITEM_NOT_FOUND
9702 ONBOARDING_CHECKLIST_ITEM_CONCURRENT_UPDATE
9703 ONBOARDING_ITEM_LIMIT_EXCEEDED
9704 ONBOARDING_IDEMPOTENCY_CONFLICT
9705 ONBOARDING_PROGRESS_CONCURRENT_UPDATE
9706 ONBOARDING_KNOWLEDGE_CONCURRENT_UPDATE

复用 FORBIDDEN、UNAUTHORIZED、BAD_REQUEST、COMPANY_NOT_FOUND。

Risks and Verification

Risk Verification
V38/V39 与 MySQL/H2 类型不一致 迁移集成测试检查三张表、唯一键、外键、种子数量与排序
企业跨租户访问条目 Service/MockMvc 测试使用两个企业,断言跨企业返回 404 且数据未变化
重复创建产生重复项 持久化测试断言相同 idempotencyKey 只有一行;不同请求体断言 9704
并发更新静默覆盖 持久化测试和乐观锁冲突测试断言 9702/9706
完成状态重复或丢失 同一学生并发/重复 PUT 测试断言唯一记录、完成率和 completedAt 正确
评价聚合暴露小样本或内部字段 DTO 白名单测试、少于 3 条测试、无知识回退测试
公司知识内容注入 HTML 请求字段按纯文本存储和返回;长度与内容验证测试
既有模块回归 扩展评价/企业相关测试后执行 mvn clean verify,并与 baseline 对比路径数