- 生产 nginx 三路径: ry-vue3 /hg/, ry-h5 /camera/, API /hg-api, .env 分环境 - 签约链接/摄像头 base-url 走 yml, 不再硬编码 /hg 与 localhost - 新增 biz_project_executor_assign (镜像 sponsor_assign) 执行方项目级分配 - 会议费用后台汇总 FeeCalcScheduler + 结算回写项目金额 + 状态流转调度 - 签到表拍照高斯模糊脱敏 extra_oss_url, 新增 PosterService/StageDeriver - 清理 biz_support_intent 旧表 (6 Java/XML 删除) - ry-h5 相机黑屏修复: 显式 video.play() + 动态 apiBase 上传 - 资源: simhei 字体 / logo.png / qrcode_1.png / favicon.ico Co-Authored-By: Claude <noreply@anthropic.com>
20 KiB
会议状态流转 —— 设计与实现总结
本文档是对「会议状态机」从单一
current_stage枚举重构为事实驱动模型的完整设计说明。 供后续维护、二次开发、排查问题时查阅。最后更新时间:2026-08-23。
1. 背景与动机
1.1 旧模型的问题
旧实现用 biz_meeting.current_stage 一个枚举字段承载整个状态机,存在三类结构性缺陷:
- 单字段多语义:一个字段既要表达「执行/审核/结算/冻结」等事实,又要表达「每个角色看到什么」,二者耦合,任何角色差异都要靠
if/else硬编码中文标签,导致历史 bug(如BizDashboardController拿枚举码去比中文标签,统计恒为 0)。 - 审核链路无法表达:材料/凭证需要「两级审核」(合规 → 支持方),单枚举
AWAITING_SPONSOR之类无法区分「合规审中」还是「支持方审中」,也无法区分「材料退回」与「凭证退回」。 - 时间驱动状态靠手改:冻结、待结算(24h)这类时间门槛没有落地字段,只能靠人工或外置脚本改状态。
1.2 新模型的核心思想
事实与展示分离:
biz_meeting只存事实(is_executed/is_frozen/is_settled/is_finished+ 材料/凭证审核阶段 + 审核时间 + 两级审核标记)。- 各角色看到的「阶段名称」由这些事实实时推导,不再是一个固定枚举投影。
类比:数据库不存「年龄」,只存「出生日期」,「年龄」是算出来的。这里
biz_meeting只存「发生了什么」,各角色看到的「当前处于什么阶段」是算出来的。
2. 数据模型(事实字段)
2.1 biz_meeting 新增/复用的状态事实字段
| 字段 | 类型 | 含义 | 谁来写 |
|---|---|---|---|
is_executed |
int(0/1) | 是否执行 | 调度器 markExecuted(start_time 到点) |
execute_time |
datetime | 执行时间 | 调度器 |
is_frozen |
int(0/1) | 是否冻结 | 调度器 markFrozen(逾期未提交) |
freeze_time |
datetime | 冻结时间 | 调度器 |
is_settled |
int(0/1) | 是否结算 | 合规/管理员点击「结算」 |
settle_time |
datetime | 结算时间 | 同上 |
is_finished |
int(0/1) | 是否完结 | 合规/管理员点击「完结」 |
finish_time |
datetime | 完结时间 | 同上 |
material_audit_stage |
varchar | 材料审核阶段(4 值,见 §3) | 执行方提交 / 审核动作 |
material_audit_time |
datetime | 材料最近一次审核动作时间 | 同上 |
material_compliance_approved |
int(0/1) | 材料合规(一级)是否通过 | 合规审核通过时置 1 |
voucher_audit_stage |
varchar | 凭证审核阶段(4 值) | 执行方提交 / 审核动作 |
voucher_audit_time |
datetime | 凭证最近一次审核动作时间 | 同上 |
voucher_compliance_approved |
int(0/1) | 凭证合规(一级)是否通过 | 合规审核通过时置 1 |
current_stage |
varchar | 派生缓存(9 值,见 §4) | 仅由 StageDeriver/控制器/调度器写,业务代码不得直接改 |
is_executed为数据库既有字段,本次直接复用,未删未重加。 新增字段默认值:material_audit_stage/voucher_audit_stage默认'NOT_SUBMITTED'(已 ALTER),其余 int 默认 0、时间默认 NULL。
2.2 审核阶段枚举(4 值)
material_audit_stage / voucher_audit_stage 共用,枚举 MeetingAuditStageEnum:
| 值 | 中文 | 含义 |
|---|---|---|
NOT_SUBMITTED |
未提交 | 执行方尚未提交(含提交后被退回、尚未重提的初始态) |
SUBMITTED |
已提交 | 已提交,处于审核链路中(合规审或支持方审) |
APPROVED |
审核通过 | 支持方(二级)审核通过 |
REJECTED |
审核驳回 | 任一级驳回,退回执行方 |
注:用户拍板「已提交」和「待审核」合并为
SUBMITTED,因此审核阶段是 4 值而非 5 值。审核链路中「合规审中 vs 支持方审中」由*_compliance_approved布尔位区分(见 §5)。
3. 两级审核(合规 → 支持方)
SUBMITTED 内部再拆两级,由 material_compliance_approved / voucher_compliance_approved 标记:
执行方提交 (SUBMITTED, compliance_approved=0)
│
▼
┌─────────────────┐
│ 合规审核 (manager) │ audit-compliance
└─────────────────┘
│ 通过 → compliance_approved=1
│ 拒绝 → REJECTED(退回执行方)
▼
┌─────────────────┐
│ 支持方审核 (监察员) │ audit-supervision
└─────────────────┘
│ 通过 → APPROVED
│ 拒绝 → REJECTED(退回执行方,并通知)
▼
APPROVED
- 材料、凭证各自独立走这套两级链路,可同时提交、同时审核(
auditType = MATERIAL|VOUCHER|BOTH)。 - 「材料合规通过 + 凭证也通过」后,若最晚审核时间已超 24h,则进入「待结算」。
4. 派生逻辑(单一可信源:StageDeriver)
后端 com.ruoyi.business.service.StageDeriver 是唯一推导逻辑所在地,前端 utils/meetingStage.js 是其镜像。
4.1 物理阶段(9 值,derivePhysicalStage)
用于 current_stage 缓存 + 列表筛选精确匹配,按优先级自上而下:
| 优先级 | 事实条件 | 物理阶段 |
|---|---|---|
| 1 | is_frozen=1 |
FROZEN 冻结中 |
| 2 | is_finished=1 或 is_settled=1 |
SETTLED 已结算 |
| 3 | material_audit_stage=REJECTED |
RECTIFYING 待整改 |
| 4 | material=APPROVED 且 settlementReady |
AWAITING_SETTLEMENT 待结算 |
| 5 | material=APPROVED 且未满足 24h |
SUPERVISION_APPROVED 审核通过 |
| 6 | material=SUBMITTED 且 material_compliance_approved=1 |
AWAITING_SUPERVISION 待支持方审核 |
| 7 | material=SUBMITTED 且 material_compliance_approved=0 |
AWAITING_COMPLIANCE 待合规审核 |
| 8 | material=NOT_SUBMITTED 且 is_executed=1 |
RUNNING 执行中 |
| 9 | material=NOT_SUBMITTED 且 is_executed=0 |
NOT_STARTED 未执行 |
settlementReady 判据(待结算,用户拍板口径):
voucher_audit_stage = APPROVED
AND max(material_audit_time, voucher_audit_time) 距今 >= 24 小时(自然日)
即:材料+凭证都通过,且最晚通过的那个时间点已超 24h。
4.2 各角色展示名(deriveDisplay)
展示层按角色折叠,优先级自上而下:
| 事实条件 | executor | sponsor(支持方) | manager(合规) | admin / doctor / expert |
|---|---|---|---|---|
is_frozen=1 |
冻结中 | 冻结中 | 冻结中 | 冻结中 |
is_finished=1 |
已完结 | 已完结 | 已完结 | 已完结 |
is_settled=1 |
已结算 | 已结算 | 已结算 | 已结算 |
material=REJECTED |
已退回 | 待整改 | 待整改 | 待整改 |
material=APPROVED 且 24h |
待结算 | 待结算 | 待结算 | 待结算 |
material=APPROVED 未满 24h |
审核通过 | 审核通过 | 审核通过 | 审核通过 |
material=SUBMITTED 且合规已过 |
待审核 | 待审核 | 审核通过 | 待审核 |
material=SUBMITTED 且合规审中 |
待审核 | 已执行未传材料 | 待审核 | 待审核 |
material=NOT_SUBMITTED 且已执行 |
执行中 | 已执行未传材料 | 已执行未传材料 | 已执行未传材料 |
material=NOT_SUBMITTED 且未执行 |
未执行 | 未执行 | 未执行 | 未执行 |
关键差异点:
- 退回:只有执行方看「已退回」,其余方看「待整改」。
- 合规审中:支持方(只读旁观者)看「已执行未传材料」。
- 支持方审中:合规(manager)看「审核通过」,其余方看「待审核」。
- 展示名优先按材料阶段驱动(用户规则:材料优先),凭证阶段只在
settlementReady判断中起作用。
5. 状态机流转总图
┌─────────────────────────────┐
│ NOT_STARTED 未执行 │
└──────────────┬──────────────┘
start_time 到点(调度器 markExecuted)
▼
┌─────────────────────────────┐
│ RUNNING 执行中 │
└──────┬───────────────┬──────┘
执行方提交材料/凭证 │ │ end_time+submit_deadline_days 到
(submit-material/ │ │ 且 material 未提交(调度器 markFrozen)
submit-voucher) │ ▼
▼ │ ┌─────────────────┐
┌──────────────────┐ │ │ FROZEN 冻结中 │(终止,不可提交)
│ SUBMITTED 已提交 │◄┘ └─────────────────┘
│ (compliance=0) │
└────────┬─────────┘
合规审核 (audit-compliance, manager)
├─ 拒绝 → REJECTED ────────────┐
└─ 通过 → compliance_approved=1 │
▼ │
┌──────────────────────┐ │
│ 支持方审核 (audit- │ │
│ supervision, 监察员) │ │
└────────┬─────────────┘ │
├─ 拒绝 → REJECTED ─────────────┤
└─ 通过 → APPROVED │
│ │
material+voucher 都 APPROVED │
│ │
┌─────────────────┴──┐ │
│ 未满 24h: │ │
│ SUPERVISION_APPROVED│ │
│ 审核通过 │ │
└────────┬──────────┘ │
满 24h(调度器 markSettlementReady)│
▼ │
┌─────────────────────┐ │
│ AWAITING_SETTLEMENT │ │
│ 待结算 │ │
└────────┬────────────┘ │
合规/管理员点「结算」(settle) │
▼ │
┌─────────────────────┐ │
│ SETTLED 已结算 │ │
└────────┬────────────┘ │
合规/管理员点「完结」(finish) │
▼ │
┌─────────────────────┐ │
│ 已完结 (is_finished) │ │
└─────────────────────┘ │
│
REJECTED 退回执行方 → 执行方看「已退回」, 可重新提交 ◄──┘
(material/voucher 回到 NOT_SUBMITTED 语义, 重新走 SUBMITTED)
6. 后端改动清单
| 文件 | 改动 |
|---|---|
service/StageDeriver.java(新增) |
单一可信源:derivePhysicalStage + deriveDisplay + settlementReady |
domain/BizMeeting.java |
新增 12 个事实字段 + getter/setter |
mapper/BizMeetingMapper.java + .xml |
resultMap/selectFields/updateByPrimaryKey 补 12 字段;调度 SQL 重写为 3 个 update |
scheduler/MeetingStageScheduler.java(重写) |
3 个 @Scheduled 每分钟任务:markExecuted / markFrozen / markSettlementReady |
controller/BizMeetingController.java |
重写审核段(见 §7);新增 StageDeriver 注入与 appendAuditLog 4 列版本 |
service/impl/BizMeetingServiceImpl.java |
新建会议默认 material/voucher_audit_stage = 'NOT_SUBMITTED'(原 INIT) |
mapper/BizMeetingAuditLogMapper.java + .xml |
current_stage 拆为 4 列:executor_stage/sponsor_stage/manager_stage/admin_stage |
controller/BizDashboardController.java |
修复历史 bug:中文标签比对 → 枚举码比对 |
6.1 调度器 SQL 语义
markExecuted:start_time <= NOW()且material_audit_stage='NOT_SUBMITTED'且is_executed=0且is_frozen=0→is_executed=1, execute_time=NOW(), current_stage='RUNNING'。markFrozen:material='NOT_SUBMITTED'且date_add(end_time, interval p.submit_deadline_days day) <= NOW()→is_frozen=1, freeze_time=NOW(), current_stage='FROZEN'(submit_deadline_days取自biz_project,join 软删过滤)。markSettlementReady:材料+凭证都APPROVED且greatest(material_audit_time, voucher_audit_time) <= NOW() - INTERVAL 1 DAY→current_stage='AWAITING_SETTLEMENT'。
7. 后端接口(审核链路)
| 接口 | 角色 | 前置校验 | 通过后 | 拒绝后 |
|---|---|---|---|---|
POST /{meetingId}/submit-material |
执行方(isExecutorOfProject) |
已执行 + 未冻结 + stage∈{NOT_SUBMITTED,REJECTED} + 至少 1 条 L_* 材料 + 1 条 M_* 材料 |
material→SUBMITTED, compliance=0 | — |
POST /{meetingId}/submit-voucher |
执行方 | 已执行 + 未冻结 + stage∈{NOT_SUBMITTED,REJECTED} + 有 LV_PAYMENT + SV_PAYMENT |
voucher→SUBMITTED, compliance=0 | — |
POST /{meetingId}/audit-compliance |
合规(manager) | stage=SUBMITTED 且 compliance=0;拒绝需意见 | compliance=1(转入支持方) | →REJECTED(退回) |
POST /{meetingId}/audit-supervision |
监察员(biz_meeting_supervisor) |
stage=SUBMITTED 且 compliance=1;拒绝需意见 | →APPROVED(材料通过时一并写监管意见) | →REJECTED(退回并通知执行方) |
POST /{meetingId}/settle |
合规/管理员 | 材料+凭证都 APPROVED | is_settled=1, settle_time |
— |
POST /{meetingId}/finish |
合规/管理员 | is_settled=1 |
is_finished=1, finish_time |
— |
auditType支持MATERIAL/VOUCHER/BOTH(多选,resolveTypes展开)。- 结算 → 完结是两个独立的手动动作(用户规则 1),不能跳步。
supervision-opinion旧接口已删除,支持方审批统一走audit-supervision。
7.1 审核日志(biz_meeting_audit_log)
appendAuditLog(m, auditType, result, opinion) 不再写单一 current_stage,改为写 4 个角色列,每个记录流转那一刻各角色看到的展示态:
executor_stage / sponsor_stage / manager_stage / admin_stage
(由 stageDeriver.deriveDisplay(role, m) 实时生成)。旧 current_stage 列保留在库里但不再读写(遗留,见 §10)。
8. 前端改动清单
| 文件 | 改动 |
|---|---|
utils/meetingStage.js(重写) |
镜像 StageDeriver:derivePhysicalStage / deriveStage / stageLabel / stageClass / stageTag / STAGE_OPTIONS |
views/meetings/Meetings.vue |
admin/manager/sponsor 共享列表:stageLabel(roleSegment, row) + stageClass(row);sponsor「审批」按钮条件 derivePhysicalStage(row)==='AWAITING_SUPERVISION',点击 POST audit-supervision |
views/meetings/MeetingDetail.vue |
审核 dialog 改多选 checkbox(材料/凭证);新增「结算」「完结」按钮 + canSettle/canFinish + onSettle/onFinish;fmtAuditStage 4 值标签 |
views/executor/Meetings.vue |
stageLabel('executor', row)(列表 + 监督意见 dialog) |
views/doctor/Meetings.vue |
stageTag(row) + stageLabel('doctor', row);筛选下拉改用 STAGE_OPTIONS |
meetingStage.js与后端StageDeriver是严格镜像:任何一处改了推导规则,另一处必须同步,否则列表显示与详情/日志不一致。
9. 关键设计决策(拍板记录)
- 结算 → 完结需手动点击:
settle与finish是两个独立动作,合规/管理员各点一次。 - 待结算 24h 门槛:材料+凭证都通过且「最晚通过时间」超 24h 才进「待结算」。
- 冻结判据:
end_time + project.submit_deadline_days天;未提交判据 =material_audit_stage == NOT_SUBMITTED。 - 已提交 / 待审核合并:审核阶段 4 值(
SUBMITTED内部靠compliance_approved分两级)。 is_executed(0,1)复用:DB 已有,直接使用,未删未重加。- 材料优先:展示名优先按材料阶段驱动,凭证阶段仅参与
settlementReady判断。
10. 已知问题 / 待办 / 注意事项 ⚠️
- 24h 是软门槛:
settle接口和前端「结算」按钮只校验「材料+凭证都 APPROVED」,未强制校验 24h。24h 目前只体现在展示名(「审核通过」→「待结算」)和调度器翻转current_stage。若需硬校验,需给canSettle+settle加settlementReady判据。 - 执行方新增「审核通过」态:材料支持方通过后、满 24h 前,执行方看到「审核通过」,此态不在原始需求清单中,需确认是否可接受。
views/sponsor/Meetings.vue是死代码:路由未引用(sponsor 复用共享meetings/Meetings.vue),内含非法枚举AWAITING_SPONSOR、不存在的auditStatus/auditOpinion字段、直接写current_stage的「结算/解冻」逻辑。建议删除。current_stage是派生缓存:仅StageDeriver+ 控制器转换端点 + 调度器可写,任何业务代码不得直接update current_stage(写了也会被下次推导覆盖)。biz_meeting_audit_log.current_stage遗留列:新 mapper 已改用 4 角色列,旧列未删(按约定未执行 DROP)。- 未编译:本次开发未跑
mvn install/npm build。StageDeriver是新增@Component,走.m2jar 加载时需先mvn install才生效。 - 前端/后端镜像同步:
meetingStage.js与StageDeriver必须同步修改,改推导规则时两处一起改。
11. 附:涉及文件速查
后端(ry-api/ruoyi-business/.../business/)
service/StageDeriver.java(新增)controller/BizMeetingController.javascheduler/MeetingStageScheduler.javadomain/BizMeeting.javamapper/BizMeetingMapper.java+resources/mapper/business/BizMeetingMapper.xmlmapper/BizMeetingAuditLogMapper.java+.xmlservice/impl/BizMeetingServiceImpl.javacontroller/BizDashboardController.java
前端(ry-vue3/src/)
utils/meetingStage.jsviews/meetings/Meetings.vueviews/meetings/MeetingDetail.vueviews/executor/Meetings.vueviews/doctor/Meetings.vue