Pharmagin 讲者平台 - 完整重构规范
版本: 2.0 | 日期: 2026-02-21 基于: 跨 15 个业务领域的 22 份分析文档
目录
- 执行摘要
- 当前系统问题
- 目标架构概述
- 领域重构
- 4.1 项目生命周期 (Program Lifecycle)
- 4.2 讲者管理 (Speaker Management)
- 4.3 参会者与注册 (Attendee & Registration)
- 4.4 预算与财务 (Budget & Financial)
- 4.5 合规与监管 (Compliance & Regulatory)
- 4.6 用户与权限 (User & Permission)
- 4.7 沟通与通知 (Communication & Notification)
- 4.8 地理与管辖区 (Geographic & Territory)
- 4.9 报告与分析 (Reporting & Analytics)
- 4.10 配置与特性管理 (Configuration & Feature Management)
- 4.11 内容与文档 (Content & Document)
- 4.12 问卷与反馈 (Survey & Feedback)
- 4.13 费用管理 (Expense Management)
- 4.14 虚拟项目 (Virtual Program)
- 4.15 集成 (Integration)
- 统一数据模型
- API 架构
- 前端架构
- 安全架构
- 基础设施与 DevOps
- 迁移策略
- 实施路线图
1. 执行摘要
1.1 平台目的
Pharmagin 是一个医药讲者项目管理平台,支持以下角色:
- 代理策划团队 (Agency Planners) 配置、管理并结算讲者项目
- 销售代表 (Sales Representatives) 提交项目申请并管理讲者互动
- 讲者 (Speakers/HCPs) 管理个人资料、合同、培训及费用
1.2 当前系统规模
| 指标 | 数量 |
|---|---|
| 后端模块 | 37 |
| 数据库实体 | 138+ |
| 前端应用 | 3 (Plannerview, Salesview, Speakerview) |
| 外部集成 | 12 |
| 特性开关(Feature flags) | 165+ |
| API 端点 | ~200+ |
| 业务规则 | 123+ |
| 已知问题 | 332 |
1.3 为什么要重写
当前系统存在以下问题:
- 18 个严重的安全漏洞(SQL 注入、RCE、凭证暴露、缺少鉴权)
- 架构技术债:12 个上帝类(600-2100+ 行),包含 98 个字段的实体,散落各处的硬编码状态机
- 可扩展性瓶颈:内存鉴权令牌、本地文件系统存储、线程不安全的单例
- 数据模型不一致:7 种不同的软删除模式、10+ 个无类型的 JSONB 字段、缺少主键 (PK)
- 技术栈老旧:React 15-16.x, Ant Design 3.x, Spring Boot 2.1.7 (已终止支持)
- 缺乏正式的状态机:所有状态转换在服务层中以硬编码的 if-else 形式存在
1.4 重写目标
- 安全第一:消除所有 18 个严重漏洞;每个端点强制实施 RBAC(基于角色的访问控制)
- 领域驱动设计 (DDD):将上帝实体分解为有界上下文 (Bounded Contexts)
- 生命周期状态机:引入正式的状态机并结合分阶段的锁定机制
- 基于品牌的预算:预算分配由品牌(药物)驱动,而不仅仅依靠地理位置
- 统一组织架构层级:用灵活的 OrgNode 树取代硬编码的 4 层地理结构
- 现代化技术栈:Spring Boot 3.x, React 18+, TypeScript, Vite
- 水平扩展能力:无状态 JWT、分布式缓存、对象存储
2. 当前系统问题
2.1 严重安全问题 (P0)
| ID | 问题 | 所在位置 | 风险 |
|---|---|---|---|
| S-01 | SQL 注入 | SiteController, FileService, ExpenseMapper, Salesforce SOQL | 数据泄露 |
| S-02 | 远程代码执行 (RCE) | Custom Reports 的 ProcessBuilder | 服务器被完全攻陷 |
| S-03 | API 密钥暴露 | /v1/public/configuration 暴露 SendGrid/SFTP/API 密钥 | 凭证被盗 |
| S-04 | 未经过滤的数据导出 | 执行绕过权限过滤器的原生 SQL | 数据外发 |
| S-05 | SSO Cookie 不安全 | 缺失 HttpOnly/Secure/SameSite 标记 | 会话劫持 |
| S-06 | 遗留的明文密码 | 旧版密码比对逻辑仍在起效 | 账户被盗 |
| S-07 | JWT 声明未校验 | External-Authorization 缺少受众 (audience) 和过期校验 | 令牌伪造 |
| S-08 | 8 个未受保护的 Controller | User, Role, Geography, SalesForce, Team, Content, File, Compliance | 未经授权的访问 |
2.2 架构反模式 (Anti-Patterns)
| 模式 | 实例数 | 影响 |
|---|---|---|
| 上帝类 (>600 行) | 12 | AttendeeService (2100+), ProgramService (1577), ProductReportService (1430) |
| 上帝实体 (>70 个字段) | 4 | MeetingRequest (98), Speaker (88), Meeting (77), Attendee (70+) |
| 命名混乱 | 6+ | MeetingRequest 就是 Program, Meeting 就是 RegistrationSite, SalesTeam 就是 SalesForce |
| 滥用 JSONB (无类型) | 10+ | approvals, dynamicFields, virtualProgramInfo, content, config |
| 硬编码值 | 15+ | productId==24, categoryId 9/4, 财年 2025, DST 范围 2017-2030 |
| 缺失 @Id | 5 | MeetingChangeLog, FiscalYear, AttendeeSurveyResponse, UserProductRole, SiteTemplate |
| 软删除模式不一致 | 7 种 | Boolean, Integer, String, status 字段, del_flag, deleted, 以及硬编码 |
| 双重路口 | 7 | /meetings 与 /programs 混用, /site 与 /registration 混用等 |
2.3 可扩展性瓶颈
- 基于内存的 Guava Cache 管理鉴权令牌(无法水平扩展)
- 本地文件系统 存储文件(无法多节点部署)
- 线程不安全的单例(FieldMappingFactory, SfdcFacade, ZoomAccessTokenManager)
- SimpleDateFormat 实例变量(存在竞态条件)
- 原生 Thread() 用于文档生成(没有线程池)
- 无消息队列(邮件发送采用发后即忘的 @Async)
- 无分布式缓存(重启服务会丢失会话)
- 无 API 网关(各服务自行实现鉴权/限流)
2.4 数据完整性风险
- 缺少事务处理:区域/管辖区重新分配、参会者回复保存、Agg Spend 配置、预算分配
- 硬删除:Speaker, Expense, MeetingAlert(附带空 Example 会导致删除全体数据)
- 孤儿数据:t_user_auth_token 无上限膨胀、临时文件泄漏、历史表数据堆积
- 25% 的参会者未对账率,导致项目无法结算
- 85% 是 NULL 的 sign_in_status(创建时从未初始化)
3. 目标架构概述
3.1 架构原则
- 有界上下文 (Bounded Contexts):每个领域拥有专属实体、服务与 API
- 事件驱动 (Event-Driven):利用领域事件实现跨域通信
- 状态机优先 (State Machine First):所有生命周期类实体都采用正式的状态机
- 渐进式锁定 (Progressive Locking):随着生命周期推进,各项权限逐步收窄
- 品牌优先预算 (Brand-First Budget):预算层级自品牌起,其次才是地理位置
- 配置即数据 (Configuration as Data):特性的开关存储在数据库中,支持运行时切换
- 默认安全 (Security by Default):所有端点默认加上 @PreAuthorize,所有查询采用参数化
3.2 技术栈
| 层级 | 当前 | 目标 |
|---|---|---|
| Java | 8 | 17+ |
| Spring Boot | 2.1.7 | 3.2+ |
| Spring Security | OAuth2/JWT (自定义) | Spring Security 6 + OAuth2 Resource Server |
| ORM | MyBatis + tk.mybatis | MyBatis-Plus 或 jOOQ |
| 数据库 | PostgreSQL | PostgreSQL 15+ |
| 缓存 | Guava (基于内存) | Redis |
| 消息队列 | 无 | RabbitMQ 或 SQS |
| 文件存储 | 本地文件系统 | S3/MinIO |
| 配置 | Spring Cloud Config (YAML) | 数据库为主 + Spring Cloud Config (降级方案) |
| React | 15-16.x | 18+ |
| UI 框架 | Ant Design 3.x | Ant Design 5.x |
| 路由 | React Router 3-4.x | React Router 6+ |
| 状态管理 | Redux (手动模板代码) | Redux Toolkit + RTK Query |
| 构建工具 | CRACO | Vite |
| 语言 | JavaScript | TypeScript |
| 微前端 | Single-SPA (SystemJS) | Module Federation 或是 Single-SPA (ES modules) |
3.3 服务架构
┌─────────────────┐
│ API 网关 │ (鉴权, 限流, 路由)
│ (Spring Cloud │
│ Gateway) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────────▼───┐ ┌──────▼──────┐ ┌───▼────────┐
│ pharmagin │ │ pharmagin │ │ pharmagin │
│ -web │ │ -auth │ │ -worker │
│ (REST API) │ │ (SSO+JWT) │ │ (Async) │
└─────┬──────┘ └─────────────┘ └─────┬──────┘
│ │
┌─────▼──────────────────────────────────▼─────┐
│ PostgreSQL │
│ schemas: ooto, public, target │
└──────────────┬─────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌──▼──┐ ┌─────▼──┐ ┌─────▼────┐
│Redis │ │S3/MinIO│ │RabbitMQ │
└──────┘ └────────┘ └──────────┘服务职责:
- pharmagin-web: 核心 REST API (处理全部 15 个领域)
- pharmagin-auth: 统一的 SSO 登录中心(合并 sso 与 login)+ 生成 / 验证 JWT
- pharmagin-worker: 异步任务节点(发送邮件, 数据报表, 第三方集成, Cron 调度)
- pharmagin-config: Spring Cloud Config Server (保留用于非核心的 YAML 降级)
3.4 领域划分视图 (有界上下文)
(译注:图表同原文档一致,分为核心业务领域、支撑领域和集成层)
4. 领域重构
4.1 项目生命周期领域 (Program Lifecycle)
4.1.1 实体更名
| 现名 | 新名 | 理由 |
|---|---|---|
| MeetingRequest | Program | 反映真实的业务概念 |
| Meeting | RegistrationSite | 澄清此实体只扮演注册门户或站点的角色 |
| MeetingProgramType | ProgramType | 简化命名 |
| ProgramServiceType | ServiceType | 简化命名 |
| MeetingChangeLog | ProgramChangeLog | 命名一致性 |
| MeetingProjectTask | ProgramTask | 命名一致性 |
4.1.2 实体拆解
当前: MeetingRequest (包含 98 个字段) - 标准上帝实体。 目标: 拆解为职能单一的多个实体。拆分后的表涵盖 Program核心、ProgramLocation(地点)、ProgramRequestor(申请人)、ProgramSpeaker(1对多分配讲者)、ProgramFinancial(财务估算)、ProgramApproval(1对多审批明细)、ProgramCustomField(1对多自定义字段)。
(详细字段设计参考原英文文档)
4.1.3 项目状态机 (14 个状态)
引入一套严谨的 14 个阶段状态机,彻底替换混乱的 old_status 管理法。
核心状态: DRAFT, WAITLISTED, PENDING_APPROVAL, DENIED, PLANNING, REGISTRATION_OPEN, REGISTRATION_CLOSED, EVENT_COMPLETE, RECONCILED, CLOSED, CANCELLED, POSTPONED, VOID, REOPENED.
预算阶段 (Budget Version) 会直接由以上状态得出 (SOW、EST、BILL、ACT、CXL),取消数据库单独字段的冗余存储。
4.1.4 渐进式权限矩阵 (MeetingPhaseGuard)
在服务端实施一个核心权限门控。通过矩阵判断:例如处于 PLANNING 时,可以编辑讲者和预算;但到了 REGISTRATION_CLOSED 时刻,参会者和基础信息均被锁定,此时重点转入现场活动及对账。
4.1.5 自动状态切换
基于时间的触发器(Cron 作业,每小时运行):自动关停注册,或在会议时间结束后自动转为 EVENT_COMPLETE。 基于条件的流转:依赖系统通知,提醒执行结算步骤(计算 TOV、确认预算等于)。
4.1.6 移除重复状态字段
原系统中的 budget_version_id, budget_status, reg_site_status, attendee_list_status 都会被合并至统一的 Program Status 判断中。
4.1.7 项目结算就绪状态 API
提供 /api/v2/programs/{id}/close-out-readiness 用于一键审计项目是否具备完结 (Closed) 的条件(如对账、签到、TOV 是不是全部完成)。
4.2 讲者管理领域 (Speaker Management)
4.2.1 实体拆解
把含有 88 个字段的 Speaker 原表拆为核心 Speaker 表、SpeakerAddress(地址库)、SpeakerFinancial(1对1财务信息)、SpeakerCredential(医疗执照、证书等 1对多集合)和 SpeakerProfile(个性化偏好及履历)。
4.2.2 讲者合同模型重构
原硬编码了超过 15 个酬金字段的表,替换为了 SpeakerContract 加 1对多的 ContractHonorariaItem 以及 ContractTerritory 以支持灵活的酬金设定。
4.2.3 状态机
精简了不必要的状态,确立为: DRAFT (草稿) → ONBOARDING (补充资料中) → CONTRACT_PENDING (等签) → ACTIVE (激活生效) → SUSPENDED (临时冻结) → EXPIRED (过期)。
4.3 参会者与注册领域 (Attendee & Registration)
- 优化表字段,抽取单独的
AttendeeSignature及AttendeeVirtualInfo。 - 确立唯一的注册状态枚举流转过程。
- 对账服务 (Reconciliation) 统一化:利用策略模式,整合原来的三种对账方式 (NPI, Target, Salesforce);满足匹配要求后自动将状态判定为
RECONCILED。
4.4 预算与财务领域 (Budget & Financial)
4.4.1 “品牌优先”的预算模型
问题:当前系统以区域为主进行下拨,不符合实际。 目标:设立多层预算池体系: t_budget_pool 顶部为品牌母池,向下分配至 大区/销区。并且统一记录每次流动的 t_budget_transaction (划拨/调整审计轨迹),和各项对应消耗 t_budget_consumption。
4.4.2 预算约束 (Cap)
将判断维度划分为纯粹的两股独立维度:cap_type (金额/数量/两者皆限) 和 sharing_mode (指定/跨类型共享)。
4.4.3 后向兼容
为遗留的 API 创建诸如 v_legacy_region_budget 等数据库视图。
4.5 合规与监管领域 (Compliance & Regulatory)
- 价值转移 (TOV) 计算:必须在项目处于 RECONCILED 和名单确认后才可计算。公式:
总审计后花费 / 人数。可进行修改追踪。 - 汇总统计 (Aggregate Spend):支持更多灵活报表的映射输出。
- 文档审计检查单:标准化了项目文档的检查审核流程表。
4.6 用户与权限领域 (User & Permission)
4.6.1 认证架构更新
利用 Redis 存贮会话信息以及基于 JWT 做无状态访问。提供用于刷新的 Refresh Token (7天),并将 Access Token 生命周期控制在 15 分钟内。
4.6.2 统一的 RBAC (基于角色的权限访问控制)
把原来各自独立的 Role 双系统合成一套。增加资源限制维度 (实例级别 / 公司级别 / 品牌全网 / 特定管辖区级别)。对于 API 强制加设 @PreAuthorize 保护并实现基于权限等级及地域的 DataScopeFilter 过滤。
4.7 沟通与通知领域 (Communication & Notification)
- 统一通知存储至
t_notification,代替分散各个业务实体的通知系统。 - 引入可靠的电子邮件发送架构:
请求 → RabbitMQ → pharmagin-worker → SendGrid,附带错误追踪与重试,发送状态存在t_email_log。 - 将动态模板定义移到单独的
t_email_template引擎进行预置字段替换渲染。
4.8 地理与管辖区 (Geographic & Territory)
采用更加灵活的 t_org_node 树代替了当前写死的 SalesForce 层级:可以完美承载不同规模药企的多层树结构;基于生效日期、物化路径(Materialized path)加速查询,通过沿着节点树往上寻找最近的 Planner 分配配置。
4.9 报告与分析 (Reporting & Analytics)
摒弃调用原始 Python/R 脚本、裸 SQL,改为 Java 构建的统合报表引擎。耗时长的报表采用异步队列处理,存入 S3/MinIO 给终端下载;并把原本静态的自定义字段声明全面数据库化。
4.10 配置与特性管理 (Configuration & Feature Management)
将 165+ 个分散且经常需要重启才生效的配置全部放入数据库 t_feature_flag;进行布尔状态变量正向化命名清理 (disable → enabled)。安全方面剥离任何涉及公开配置界面的敏感串,严格分成普通展示接口和鉴权 Admin 接口。
4.11 内容与文档 (Content & Document)
- 统一所有的媒体形态进
t_asset。 - 彻底将存储从本地切换至云端存储 S3/MinIO。
- 修复安全漏洞:对文件执行参数化处理,上传端进行严苛校验(后缀、大小、真实MIME 类型),取缔直接展示文件目录的原型调试接口。
4.12 问卷与反馈 (Survey & Feedback)
- 前后端去关联解耦:不再强依赖硬编码针对不同公司的 Survey 表单处理。
Survey中由JSONB做内容预设,全部统一用 DTO 来接纳解析;所有的问卷表均带上主键并迁移入单一问卷存储架构下。
4.13 费用管理 (Expense Management)
增加独立状态机审批 (Draft → 待审批 → 审批完成/退回)。硬性规定系统利用项合计值计算得出总支费用,而不再由前端传递该数字。 把发票直接上传到文件模块;不再在 Expense 中冗余存 base64 体。
4.14 虚拟项目 (Virtual Program)
建立两张专用于存储会话数据的独立表,并提供更好的 Zoom 集成代码及每个产品对应的 Token 管理(废弃之前的单例串扰);提供第三方的简单 URL 会议集成入口。
4.15 集成领域 (Integration)
- Salesforce: 转投更快的 REST API
- 使用 Resilience4j 包裹所有第三方系统的调用(熔断、限流、重试防呆保护)。
- 密码存储: yaml 内文变为诸如 AWS Secrets Manager 等秘钥引用。
5. 统一数据模型
主要改动原则:遵循统一 t_ 开头的表名、id (BIGINT 类型) 标准、强一致 deleted 表示软除标识、取消冗余表等。
6. API 架构
- 按领域拆成了命名干净的一组 REST 端点 (
/api/v2/{domain}/{resource}) - 推荐游标式分页查询。采用
RFC 7807报错反馈范式。 - 修改并触发状态的方法全部集中在
PATCH /api/v2/programs/{id}/status,拒绝其他野路子的修改途径,利用异常报错反馈哪些数据环节仍旧卡点。
7. 前端架构
全面将 CRACO、JS 和 React 15 转成更快的并采用 TypeScript 的 Vite, React 18, Ant Design 5 全家桶架构; 创建独立的公共组件包 @pharmagin/ui 以避免在 Plannerview 等四个项目中各写一套冗余内容,术语上也全部收束为 Program 和 Registration Site。
8. 安全架构
- 鉴权依赖 JWT + Redis
- 核心漏洞彻底干预:针对 RCE 删除 ProcessBuilder,把所有裸 SQL 和注入替换为预编译安全框架。加入
XSS、Upload及SFTP后门检测方案;端点鉴权防护达到全覆盖。
9. 基础设施与 DevOps
清晰化梳理各个容器件关系 (Web、Job Worker 等服务独立,挂载 Redis/PostgreSQL 及 MQ 体系),强制采用 Actuator/Grafana 埋点追踪、执行 MDC 相关请求 ID,为以后的弹性伸缩提供可能。
10. 迁移策略
按照高保真但低风险的周期性执行路线:
- 阶段 0 (立即执行):处理当前系统的各种要命安保漏洞;无数据库形态修改。
- 阶段 1:铺设后端的底盘基建;引入基础状态机机制。
- 阶段 2:高风险。进行原上帝实体表的各种手术拆分,做老表视图代理回老数据。
- 阶段 3:重构并替换组织结构和预算分配池逻辑。
- 阶段 4:前端架构拆分,组件复用,推陈出新。
- 阶段 5/6:第三方集成与报表功能的现代翻修。
11. 实施路线图
按照 P0 至 P3 分发工作优先级;设定各项业务漏洞消除指征、覆盖度指征用于验证成果。并严格按照测试、兼容保留和逐级发布作为主要的降低风险机制。
(本文完)