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;版本不一致返回 409ONBOARDING_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 对比路径数 |