Skip to content

Configuration & Features Domain - Deep Dive Analysis

1. Domain Overview

1.1 领域职责描述

Configuration & Features 领域是整个 Speaker Platform 的"控制中枢",负责管理平台的所有可配置行为。该领域承担以下核心职责:

  1. 实例级配置管理 (Agency Configuration) - 通过 Spring Cloud Config (YAML) 管理每个客户部署实例的全局配置
  2. 产品级配置管理 (Product Configuration) - 每个药企客户的产品线独立配置,包含 100+ 个功能开关
  3. 公司级配置管理 (Company Configuration) - 面向 Speaker 的品牌定制(logo、主题色、导航栏等)
  4. 字典数据管理 (Dictionary) - 业务下拉列表数据(Product、Brand、Topic、ProgramType、Region 等)
  5. 功能开关系统 (Feature Flags) - 控制功能模块的启用/禁用
  6. UI 标签定制 (Custom Labels) - 允许客户自定义 UI 标签文案
  7. 主题/品牌系统 (Theme/Branding) - 支持客户自定义视觉风格

1.2 涉及的后端模块和包

模块/包路径说明
config 模块modules/v1/config/配置数据模型和 REST API
dict 模块modules/v1/dict/字典数据 CRUD 和查询
product 模块modules/v1/product/产品配置(Logo、注册状态等)
entity 包common/persistence/entity/数据库实体映射
config-repopharmagin-config-repo/Spring Cloud Config YAML 文件
plus 包com/pharmagin/plus/Pharmagin Plus 集成配置

2. Data Model Analysis

2.1 Entity Overview Table

2.1.1 数据库实体 (Database Entities)

EntityTable主键关键字段文件位置
Productt_productproduct_id (sequence: s_product)productName, company, companyId, logoPath, meetingSequence, crossContext, npiTarget, speakerLevel, districtOrder, districtSwitch, regActiveStatus, approvalCopyRm, salesforceUsername, salesforceSecurityToken, salesTeam, productConfig(JSONB), plidSequencecommon/persistence/entity/Product.java
ProductConfigurationt_product_configurationproduct_idregistrationStatus, logos(JSONB)common/persistence/entity/ProductConfiguration.java
Brandt_brandbrand_id (sequence: s_brand)brandName, productId, statuscommon/persistence/entity/Brand.java
Topict_topictopic_id (sequence: s_topic)topicName, topicRef, topicDescription, productId, meetingType, serviceType, status, topicCode, shortNamecommon/persistence/entity/Topic.java
Themet_themeid (sequence: t_theme_id_seq)name, content(JSONB), createdAt, createdBy, updatedAt, updatedBycommon/persistence/entity/Theme.java
MeetingProgramTypet_meeting_program_typeprogram_type_id (sequence: s_program)programTypeName, description, productId, meetingIdPrefix, projectCode, year, needApproval, meetingId, signatureText, status, sequence, brandId, userId, approvals(JSONB), color, fieldExhibitcommon/persistence/entity/MeetingProgramType.java
ProgramServiceTypet_meeting_program_service_typeprogram_service_id (sequence: s_service_type)programServiceTypeName, productId, programTypeId, description, managementFee, credit, programCategory, status, sequence, accessLevel, meetingIdPrefix, projectCode, updatedAt, virtualServiceTypeId, spaceConfig(JSONB), secondSpeakerRequired, virtualProgramInfo(JSONB), hybridcommon/persistence/entity/ProgramServiceType.java
Teamt_teamteam_id (sequence: s_team)teamName, productId, status, salesTeamId, salesForceIdcommon/persistence/entity/Team.java
TeamBrand(关联表)-teamId, brandIdcommon/persistence/entity/TeamBrand.java
TeamProgramType(关联表)-teamId, programTypeIdcommon/persistence/entity/TeamProgramType.java
SalesTeamt_sales_teamsales_team_id (sequence: s_sales_team)salesTeamName, productId, status, speakerProgramReportDisabledcommon/persistence/entity/SalesTeam.java
Regiont_regionregion_id (sequence: s_region)regionName, productId, salesTeamId, statuscommon/persistence/entity/Region.java
AttendanceConfigt_attendance_configprogram_type_idtopicEnabled, speakerEnabled, allowedAttendCount, frequencyPeriod, prohibitTrainedSpeaker, frequencyCheckEnabled, frequencyPeriodType, createdAt/By, updatedAt/Bycommon/persistence/entity/AttendanceConfig.java
ProductRolet_product_rolerole_idproductId, roleName, displayNamecommon/persistence/entity/ProductRole.java
ProductRolePermission(关联表)-roleId, permissionIdcommon/persistence/entity/ProductRolePermission.java

2.1.2 YAML 配置模型 (Spring Cloud Config POJOs)

配置类配置前缀关键属性文件位置
AgencyConfigurationagencyname, url, pvUrl, files, aspose, company, companies, products, projectTaskEnabled, expenseIdPrefix, optionalMeetingFields, enableScheduledReports, enableApprovalFlow, enableAutomatedEmail, enablePreferredSpeakersList, enableForceTargetInvitation, enableHonorariaAmount, enableCloseOutProgram, enableUnsubscribedEmail, enableDataExceptions, enableAttendeeTrackerDataToSftp, enableMosaicIntegration, enablePorzioAggSpendReport, enableGoogleGeocodeAPI, sftp, dataExtract, invitedAttendeesReminder, frequencyCheckReminder, podioWebhookUrl, sendGrid, googlemodules/v1/config/AgencyConfiguration.java
CompanyConfigurationagency.companies.{id}url, mileageReimbursementRate, companyFromAlias, disableSurveyAlerts, customLabel, logo, loginFooter, footer, companyName, referenceUrl, optionalContractFormFields, excludedContractTableFields, speakerLevels, specialities, speakerSurveyEnabled, disablePresentationDelete, surveyReport, theme, excludedSpeakerTableFields, disableUserGroup, navigation, enableEditProfile, welcomeText, loginWelcomeText, enableSignUp, headerText, enablePresentationMergeField, linkDescription, disablePresentationOnTraining, enableContractAdvanced, programQuantityLimit, enableLEIECheck, dynamicContractFieldsmodules/v1/config/CompanyConfiguration.java
ProductConfigurationagency.products.{id}(见下方 Feature Flags 完整清单)modules/v1/config/ProductConfiguration.java
ProgramConfigurationagency.products.{id}.programeditProgram, editProgramOnlyApproved, approvalEnabled, label, field, serviceOptions, thanksMessage, attachmentsEnabled, shareProgramEnabled, cancelProgramEnabled, list, enableApproverComment, dynamicProgramFields, lunchAndLearnProgram, enableProgramPresentations, disabledProgramTimeOffset, programDurationmodules/v1/config/ProgramConfiguration.java
RegistrationConfigurationagency.products.{id}.registrationopenRegistrationEnabled, registrationSiteEnabled, closeAttendeeListEnabled, closeAttendeeListPendingStatusCheckEnabled, copyAttendeesEnabled, updateCRMAttendeesEnabled, setRegistrationStatusEnabled, unreconcileEnabled, deleteAttendeeEnabled, addToSalesforceEnabled, enableEmailInvitation, allowSalesRepChangeEmailAlias, useDefaultStatusAsNextStatusForInvitedStatus, enableRegistrationSiteStatus, enableLookUpNPI, enableBatchRegistrationBasedOnAffiliation, enableBatchRegistration, enableCustomizedBrandPrefix, disableQuickRegistrationmodules/v1/config/RegistrationConfiguration.java
SpeakerSearchConfigurationagency.products.{id}.speakerSearchfilterEnabled, stateEnabled, specialityEnabled, regionReadOnly, honorariaCapEnabled, availableAmountEnabled, continueWithoutSpeakerEnabled, ratingEnabled, trainingEnabled, requiredNoticeEnabled, unavailableDaysEnabled, otherLanguageEnabled, localHonorariaEnabled, travelHonorariaEnabled, cvConfirmMessage, disableNotes, enableSpeakerCategory, enableSpeakerLevelmodules/v1/config/SpeakerSearchConfiguration.java
VirtualProgramConfigagency.products.{id}.virtualPrograms.{key}s2sOAuthTokenUrl, s2sAccountId, s2sClientId, s2sClientSecret, meetingSDKClientId, meetingSDKClientSecret, eventSecretToken, url, users, programSettings, polls, name, disableResendInvite, disableManualAttendeeAddmodules/v1/config/VirtualProgramConfig.java

2.2 Table Relationships (ER Diagram - ASCII)

                                    ┌──────────────────────┐
                                    │    t_product          │
                                    │    (Product)          │
                                    ├──────────────────────┤
                                    │ PK product_id        │
                                    │    product_name      │
                                    │ FK company_id ──────►│ (t_company - implicit)
                                    │    product_config    │ ◄── JSONB: dynamic config
                                    │    salesforce_*      │
                                    └────────┬─────────────┘
                                             │ 1

                    ┌────────────────────────┼────────────────────────┐
                    │                        │                        │
                    ▼ N                      ▼ N                      ▼ 1
        ┌───────────────────┐   ┌───────────────────┐   ┌──────────────────────┐
        │   t_brand         │   │ t_meeting_program_ │   │ t_product_           │
        │   (Brand)         │   │ type               │   │ configuration        │
        ├───────────────────┤   │ (MeetingProgramType│   │ (ProductConfiguration│
        │ PK brand_id       │   ├───────────────────┤   ├──────────────────────┤
        │    brand_name     │   │ PK program_type_id │   │ PK product_id (FK)   │
        │ FK product_id     │   │    program_type_   │   │    registration_     │
        │    status         │   │    name            │   │    status            │
        └───────┬───────────┘   │ FK product_id      │   │    logos (JSONB)     │
                │               │ FK brand_id ───────┤►  └──────────────────────┘
                │               │    need_approval   │
                │               │    approvals(JSONB)│
                │               │    color           │
                │               │    field_exhibit   │
                │               └────────┬───────────┘
                │                        │ 1
                │                        │
                │                        ▼ N
                │            ┌───────────────────────┐
                │            │ t_meeting_program_    │
                │            │ service_type          │
                │            │ (ProgramServiceType)  │
                │            ├───────────────────────┤
                │            │ PK program_service_id │
                │            │ FK product_id         │
                │            │ FK program_type_id    │
                │            │    management_fee     │
                │            │    virtual_service_   │
                │            │    type_id            │
                │            │    space_config(JSONB)│
                │            │    hybrid             │
                │            └───────────────────────┘

                │          ┌───────────────────┐
                │          │   t_topic          │
                │          │   (Topic)          │
                │          ├───────────────────┤
                │          │ PK topic_id       │
                │          │    topic_name     │
                │          │ FK product_id     │
                │          │    meeting_type   │
                │          │    service_type   │
                │          │    status         │
                │          └───────────────────┘

                ▼                              ┌──────────────────┐
        ┌───────────────┐                      │   t_sales_team   │
        │ t_team_brand  │ (关联表)              │  (SalesTeam)     │
        │ (TeamBrand)   │                      ├──────────────────┤
        ├───────────────┤                      │ PK sales_team_id │
        │ FK team_id    │◄──┐                  │    sales_team_   │
        │ FK brand_id   │   │                  │    name          │
        └───────────────┘   │                  │ FK product_id    │
                            │                  └────────┬─────────┘
        ┌───────────────┐   │                           │
        │t_team_program │   │                           │
        │  _type        │   │                           │
        │(TeamProgType) │   │                           │
        ├───────────────┤   │                           │
        │ FK team_id    │◄──┤                           │
        │ FK program_   │   │                           │
        │    type_id    │   │                           │
        └───────────────┘   │                           │
                            │                           │
                    ┌───────┴────────┐                  │
                    │    t_team      │                  │
                    │    (Team)      │                  │
                    ├────────────────┤                  │
                    │ PK team_id     │                  │
                    │    team_name   │                  │
                    │ FK product_id  │                  │
                    │ FK sales_team  │──────────────────┘
                    │    _id         │
                    │    status      │
                    └────────────────┘

        ┌────────────────────┐      ┌───────────────────┐
        │ t_attendance_config│      │    t_theme         │
        │ (AttendanceConfig) │      │    (Theme)         │
        ├────────────────────┤      ├───────────────────┤
        │ PK program_type_id │      │ PK id             │
        │    topic_enabled   │      │    name           │
        │    speaker_enabled │      │    content (JSONB)│
        │    allowed_attend_ │      │    created_at/by  │
        │    count           │      │    updated_at/by  │
        │    frequency_*     │      └───────────────────┘
        └────────────────────┘

        ┌────────────────────┐      ┌───────────────────────┐
        │  t_product_role    │      │ t_product_role_       │
        │  (ProductRole)     │      │ permission            │
        ├────────────────────┤      │ (ProductRolePermission│
        │ PK role_id         │──1:N─┤                       │
        │ FK product_id      │      │ FK role_id            │
        │    role_name       │      │ FK permission_id      │
        │    display_name    │      └───────────────────────┘
        └────────────────────┘

2.3 Data Model Issues

DM-1: JSONB 字段滥用 - Product.productConfig

  • 文件: common/persistence/entity/Product.java:76
  • productConfig 字段类型为 Object (JSONB),存储结构未知,无 schema 验证
  • 与 YAML 的 ProductConfiguration 和 DB 的 t_product_configuration 三重配置来源混乱

DM-2: ProductConfiguration 实体与 POJO 同名冲突

  • DB 实体: common/persistence/entity/ProductConfiguration.java - 映射 t_product_configuration 表,仅 3 个字段
  • YAML POJO: modules/v1/config/ProductConfiguration.java - Spring Cloud Config 绑定,100+ 字段
  • 两个完全不同的类使用相同名称,极易混淆

DM-3: ProgramServiceType.spaceConfig JSONB 字段无类型定义

  • 文件: common/persistence/entity/ProgramServiceType.java:76
  • spaceConfigvirtualProgramInfo 均为 Object 类型 JSONB,无结构约束

DM-4: 软删除与硬删除混用

  • Brand 使用 status 字段进行软删除,但 BrandService.deleteBrand() (line 97-104) 先设置 status=0 然后调用 delete() 进行硬删除,逻辑矛盾
  • Topic 同样使用 status 字段,行为不一致

DM-5: Topic 实体未使用 Lombok

  • 文件: common/persistence/entity/Topic.java
  • 手写 getter/setter 共 140+ 行,与其他实体使用 @Data 注解的风格不一致

DM-6: Team 实体冗余外键

  • 文件: common/persistence/entity/Team.java
  • 同时有 salesTeamIdsalesForceId 两个字段,语义重叠

3. Business Flow Analysis

3.1 Core Business Flows

3.1.1 Configuration Loading (Spring Cloud Config)

                    ┌──────────────────┐
                    │ pharmagin-config │
                    │   (Port 4001)    │
                    │ Config Server    │
                    └────────┬─────────┘

                             │ reads from filesystem

                    ┌──────────────────┐
                    │pharmagin-config- │
                    │     repo/        │
                    │ ┌──────────────┐ │
                    │ │agency-dev.yml│ │   Agency-level flags
                    │ │company-dev.  │ │   Company branding
                    │ │  yml         │ │
                    │ │product-dev.  │ │   Product feature flags
                    │ │  yml         │ │
                    │ │dictionary.yml│ │   Lookup data
                    │ └──────────────┘ │
                    └────────┬─────────┘

                             │ HTTP pull at startup

                    ┌──────────────────┐
                    │ pharmagin-web    │
                    │   (Port 8000)    │
                    │                  │
                    │ @ConfigProperties│
                    │ AgencyConfig     │────► Bound to "agency" prefix
                    │  ├── companies   │     Map<Integer, CompanyConfig>
                    │  ├── products    │     Map<Integer, ProductConfig>
                    │  └── sftp, etc   │
                    └────────┬─────────┘

              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
         /v1/public/    /v1/config/     /v1/config/
         configuration  instance        products
         (no auth)      (auth required) (auth required)
              │              │              │
              ▼              ▼              ▼
         SalesView       PlannerView    PlannerView
         SpeakerView     (admin)        (admin)

3.1.2 Feature Flag System

        ┌─────────────────────────────────────────────────────────┐
        │                  Feature Flag Sources                    │
        ├─────────────────────────────────────────────────────────┤
        │                                                         │
        │  Source 1: YAML Config (pharmagin-config-repo/)         │
        │  ┌─────────────────────────────────────┐                │
        │  │ AgencyConfiguration (agency level)  │                │
        │  │  - enableScheduledReports           │                │
        │  │  - enableApprovalFlow               │                │
        │  │  - enablePreferredSpeakersList      │                │
        │  │  - projectTaskEnabled               │                │
        │  │  ... (14 flags)                     │                │
        │  └─────────────────────────────────────┘                │
        │  ┌─────────────────────────────────────┐                │
        │  │ ProductConfiguration (product level)│                │
        │  │  - brandBudgetAllocationEnabled     │                │
        │  │  - switchProductEnabled             │                │
        │  │  - presentationsEnabled             │                │
        │  │  - enablePLID                       │                │
        │  │  ... (60+ flags)                    │                │
        │  └─────────────────────────────────────┘                │
        │  ┌─────────────────────────────────────┐                │
        │  │ CompanyConfiguration (company level)│                │
        │  │  - speakerSurveyEnabled             │                │
        │  │  - enableEditProfile                │                │
        │  │  - enableSignUp                     │                │
        │  │  ... (15+ flags)                    │                │
        │  └─────────────────────────────────────┘                │
        │                                                         │
        │  Source 2: Database (t_product, t_product_configuration)│
        │  ┌─────────────────────────────────────┐                │
        │  │ Product.productConfig (JSONB)       │                │
        │  │ ProductConfiguration.registrationSt.│                │
        │  └─────────────────────────────────────┘                │
        │                                                         │
        │  Source 3: Database (t_attendance_config)               │
        │  ┌─────────────────────────────────────┐                │
        │  │ AttendanceConfig.frequencyCheckEnabl│                │
        │  │ AttendanceConfig.topicEnabled        │                │
        │  │ AttendanceConfig.speakerEnabled      │                │
        │  └─────────────────────────────────────┘                │
        └─────────────────────────────────────────────────────────┘


        ┌─────────────────────────────────────────────────────────┐
        │                Frontend Consumers                       │
        ├─────────────────────────────────────────────────────────┤
        │                                                         │
        │  PlannerView:                                           │
        │    GET /api/v1/configuration/instance                   │
        │    → Redux: global.instanceConfig                       │
        │    → Reads: enablePreferredSpeakersList,                │
        │             enableForceTargetInvitation,                │
        │             projectTaskEnabled,                         │
        │             enableUnsubscribedEmail                     │
        │                                                         │
        │  SalesView:                                             │
        │    GET /api/v1/public/configuration/product             │
        │    → Redux: globalConfig                                │
        │    → Reads: switchProductEnabled, program.*, registration.* │
        │                                                         │
        │  SpeakerView:                                           │
        │    GET /api/v1/public/configuration/company             │
        │    → Redux/sessionStorage: companyConfiguration         │
        │    → Reads: navigation.*, enableEditProfile,            │
        │             enableSignUp, speakerSurveyEnabled          │
        └─────────────────────────────────────────────────────────┘

3.1.3 Product/Brand/Topic Hierarchy

        Product (药企产品线)

          ├──► Brand (品牌)
          │     └──► TeamBrand (品牌-团队关联)

          ├──► MeetingProgramType (项目类型)
          │     │
          │     ├──► ProgramServiceType (服务选项)
          │     │     └──► virtualServiceTypeId → VirtualServiceType enum
          │     │
          │     ├──► AttendanceConfig (出席频率配置)
          │     │
          │     ├──► TeamProgramType (类型-团队关联)
          │     │
          │     └──► brandId (可选品牌关联)

          ├──► Topic (演讲主题)
          │     ├── meetingType (会议类型)
          │     └── serviceType (服务类型)

          ├──► Team (团队)
          │     ├──► TeamBrand
          │     ├──► TeamProgramType
          │     └──► salesTeamId → SalesTeam

          ├──► SalesTeam (销售团队)
          │     └──► Region (区域)
          │           └──► District (地区)
          │                 └──► Territory (辖区)

          └──► ProductRole (角色)
                └──► ProductRolePermission (权限)

3.1.4 Dictionary Data Management

        Admin Panel (PlannerView)

              │ CRUD Operations

        ┌──────────────────────────────────┐
        │   Admin Page Menus               │
        ├──────────────────────────────────┤
        │ Program Types    → POST/PUT /v1/programtypes     │
        │ Brands           → POST/PUT/DEL /v1/brands       │
        │ Sales Force      → (setup sales force)           │
        │ Teams            → POST/PUT/DEL /v1/teams        │
        │ Attendance Freq  → POST /v1/attendance           │
        │ Reg Workflow     → (registration workflow)       │
        │ Preferred Speaker→ POST /v1/preferred-speaker    │
        │ Force Target Inv → POST /v1/force-target-inv     │
        │ Logos            → PUT /v1/products/{id}/logos    │
        │ Reg Status       → PUT /v1/products/regstatus    │
        └──────────────────────────────────┘


        ┌──────────────────────────────────┐
        │   Dictionary Lookup API          │
        │   GET /v1/dictionary/*           │
        ├──────────────────────────────────┤
        │ /products                        │
        │ /companies                       │
        │ /programtypes (+ serviceOptions) │
        │ /servicetypes                    │
        │ /brands (+ teamIds)              │
        │ /teams                           │
        │ /salesforces                     │
        │ /regions                         │
        │ /districts                       │
        │ /territories                     │
        │ /speakers                        │
        │ /sales                           │
        │ /planners                        │
        │ /topics                          │
        │ /venues                          │
        │ /plids                           │
        │ /aggregate-spend-report-fields   │
        │ /district-managers               │
        │ /regional-managers               │
        └──────────────────────────────────┘

3.2 Feature Flags Inventory

3.2.1 Agency Level Feature Flags (AgencyConfiguration)

位于 modules/v1/config/AgencyConfiguration.javapharmagin-config-repo/agency-dev.yml

Flag 名称类型默认值描述状态
projectTaskEnabledbooleanfalse启用项目任务功能ACTIVE
enableScheduledReportsbooleanfalse启用定时报告功能ACTIVE
enableApprovalFlowbooleanfalse启用可配置审批流ACTIVE
enableAutomatedEmailbooleanfalse启用自动化邮件ACTIVE
enablePreferredSpeakersListbooleanfalse启用首选讲者列表ACTIVE
enableForceTargetInvitationbooleanfalse启用强制目标邀请ACTIVE
enableHonorariaAmountbooleanfalse启用酬金金额设置ACTIVE
enableCloseOutProgrambooleanfalse启用关闭项目功能ACTIVE
enableUnsubscribedEmailbooleanfalse启用退订邮件管理ACTIVE
enableDataExceptionsbooleanfalse启用数据异常追踪ACTIVE
enableAttendeeTrackerDataToSftpbooleanfalse启用参会者数据 SFTP 传输ACTIVE
enableMosaicIntegrationbooleanfalse启用 Mosaic 集成ACTIVE
enablePorzioAggSpendReportbooleanfalse启用 Porzio 聚合支出报告ACTIVE
enableGoogleGeocodeAPIbooleanfalse启用 Google 地理编码 APIACTIVE

3.2.2 Product Level Feature Flags (ProductConfiguration)

位于 modules/v1/config/ProductConfiguration.javapharmagin-config-repo/product-dev.yml

Flag 名称类型默认值描述状态
plannerIdFromProgramTypebooleanfalse按项目类型分配 Planner(非地理)ACTIVE
brandBudgetAllocationEnabledbooleanfalse启用按品牌预算分配ACTIVE
disallowProgramIfBudgetReachedbooleanfalse预算达限时禁止创建项目ACTIVE
disallowProgramSubmissionbooleanfalse禁止项目提交ACTIVE
switchProductEnabledbooleanfalse启用产品切换功能ACTIVE
presentationsEnabledbooleanfalse启用演示文稿功能ACTIVE
disablePlannerMeetingReminderbooleanfalse禁用 Planner 会议提醒ACTIVE
disableRMMeetingReminderbooleanfalse禁用区域经理会议提醒ACTIVE
kclMeetingReminderEnabledbooleanfalse启用 KCL 会议提醒ACTIVE
attendeeType2Enabledbooleanfalse启用 Type2 参会者ACTIVE
programSummaryReportRestrictionsEnabledbooleanfalse启用项目摘要报告限制ACTIVE
speakerUtilizationRestrictionsEnabledbooleanfalse启用讲者利用率报告限制ACTIVE
programSummaryByBudgetCategoryEnabledbooleanfalse按预算类别生成项目摘要报告ACTIVE
programSummaryCustomFieldsEnabledbooleanfalse项目摘要报告自定义字段ACTIVE
detailedProgramCountsEnabledbooleanfalse讲者利用率报告详细计数ACTIVE
simplifiedSpeakerListEnabledbooleanfalse简化讲者列表下载ACTIVE
disableLocalAdBoardTravelHonorariabooleanfalse禁用 Local Ad Board 差旅酬金ACTIVE
enableNewSurveybooleanfalse启用新版问卷功能ACTIVE
restrictCalendarViewByUserbooleanfalse按用户限制日历视图ACTIVE
enableCustomReportbooleanfalse启用自定义报告ACTIVE
enableSalesRepChatbooleanfalse启用销售代表聊天ACTIVE
disableChannelbooleanfalse禁用频道功能ACTIVE
enableGroupDiscussionbooleanfalse启用群组讨论ACTIVE
disableChatAcrossAttendeesbooleanfalse禁止跨参会者聊天ACTIVE
enableSpeakerNominationbooleanfalse启用讲者提名ACTIVE
enableVideoCallbooleanfalse启用音视频通话ACTIVE
enableDownloadPVLinkbooleanfalse启用 PV 下载链接ACTIVE
disableDownloadSpeakerListLinkbooleanfalse禁用下载讲者列表ACTIVE
enablePLIDbooleanfalse启用 PLID/UUID 功能ACTIVE
enableTOVCalculationNotificationbooleanfalse启用 TOV 计算邮件通知ACTIVE
disableCalendarDescriptionbooleanfalse禁用日历描述ACTIVE
enableSpeakerAutomaticRegistrationCheckbooleanfalse启用讲者自动注册检查ACTIVE
enableScheduledReportbooleanfalse启用定时报告(产品级)ACTIVE
enableSignInAttendeesbooleanfalse启用签到参会者功能ACTIVE
enableQuickEmailInvitationbooleanfalse启用快速邮件邀请ACTIVE
enableMultipleEmailInvitationbooleanfalse启用批量邮件邀请ACTIVE
enableApprovalStatusOnProgramListbooleanfalse项目列表显示审批状态ACTIVE
enableAttendanceFormatbooleanfalse启用出席格式ACTIVE
disablePresentationOnResourcebooleanfalse资源页禁用演示文稿ACTIVE
hideSupportTextbooleanfalse隐藏支持信息文本ACTIVE
enableSpeakerRandomizationbooleanfalse启用讲者随机化ACTIVE

3.2.3 Product Level - Program Configuration Flags

位于 modules/v1/config/ProgramConfiguration.java

Flag 名称类型默认值描述状态
program.editProgrambooleanfalse允许编辑项目ACTIVE
program.editProgramOnlyApprovedbooleanfalse仅允许编辑已批准项目ACTIVE
program.approvalEnabledbooleanfalse启用项目审批ACTIVE
program.attachmentsEnabledbooleanfalse启用项目附件ACTIVE
program.shareProgramEnabledbooleanfalse启用项目共享ACTIVE
program.cancelProgramEnabledbooleanfalse启用项目取消ACTIVE
program.enableApproverCommentbooleanfalse启用审批者评论ACTIVE
program.lunchAndLearnProgrambooleanfalse启用午餐学习项目检查ACTIVE
program.enableProgramPresentationsbooleanfalse启用项目演示文稿ACTIVE

3.2.4 Product Level - Program List Configuration Flags

位于 modules/v1/config/ProgramListConfiguration.java

Flag 名称类型默认值描述状态
program.list.brandEnabledbooleantrue项目列表品牌过滤ACTIVE
program.list.topicEnabledbooleantrue项目列表主题过滤ACTIVE
program.list.teamEnabledbooleanfalse项目列表团队过滤ACTIVE
program.list.venueEnabledbooleanfalse项目列表场地过滤ACTIVE
program.list.locationEnabledbooleanfalse项目列表地点过滤ACTIVE
program.list.districtManagerEnabledbooleanfalse项目列表 DM 过滤ACTIVE
program.list.regionalManagerEnabledbooleanfalse项目列表 RM 过滤ACTIVE

3.2.5 Product Level - Registration Configuration Flags

位于 modules/v1/config/RegistrationConfiguration.java

Flag 名称类型默认值描述状态
registration.openRegistrationEnabledbooleanfalse启用开放注册ACTIVE
registration.registrationSiteEnabledbooleanfalse启用注册站点链接ACTIVE
registration.closeAttendeeListEnabledbooleanfalse启用关闭参会者列表ACTIVE
registration.closeAttendeeListPendingStatusCheckEnabledbooleanfalse关闭时检查待定状态ACTIVE
registration.copyAttendeesEnabledbooleanfalse启用复制参会者ACTIVE
registration.updateCRMAttendeesEnabledbooleanfalse启用更新 CRM 参会者ACTIVE
registration.setRegistrationStatusEnabledbooleanfalse启用设置注册状态ACTIVE
registration.unreconcileEnabledbooleanfalse启用反核销ACTIVE
registration.deleteAttendeeEnabledbooleanfalse启用删除参会者ACTIVE
registration.addToSalesforceEnabledbooleanfalse启用添加到 SalesforceACTIVE
registration.enableEmailInvitationbooleanfalse启用邮件邀请ACTIVE
registration.allowSalesRepChangeEmailAliasbooleanfalse允许销售代表修改邮件别名ACTIVE
registration.useDefaultStatusAsNextStatusForInvitedStatusbooleanfalse将默认状态作为邀请状态的下一状态ACTIVE
registration.enableRegistrationSiteStatusbooleanfalse启用注册站点状态显示ACTIVE
registration.enableLookUpNPIbooleanfalse启用 NPI 查询ACTIVE
registration.enableBatchRegistrationBasedOnAffiliationbooleanfalse启用基于从属关系的批量注册ACTIVE
registration.enableBatchRegistrationbooleanfalse启用批量注册ACTIVE
registration.enableCustomizedBrandPrefixbooleanfalse启用自定义品牌前缀ACTIVE
registration.disableQuickRegistrationbooleanfalse禁用快速注册ACTIVE

3.2.6 Product Level - Speaker Search Configuration Flags

位于 modules/v1/config/SpeakerSearchConfiguration.java

Flag 名称类型默认值描述状态
speakerSearch.filterEnabledbooleanfalse启用讲者组过滤ACTIVE
speakerSearch.stateEnabledbooleanfalse启用州过滤ACTIVE
speakerSearch.specialityEnabledbooleanfalse启用专科过滤ACTIVE
speakerSearch.regionReadOnlybooleanfalse区域只读ACTIVE
speakerSearch.honorariaCapEnabledbooleanfalse显示酬金上限状态ACTIVE
speakerSearch.availableAmountEnabledbooleanfalse显示可用酬金金额ACTIVE
speakerSearch.continueWithoutSpeakerEnabledbooleanfalse允许无讲者继续ACTIVE
speakerSearch.ratingEnabledbooleanfalse启用讲者评分ACTIVE
speakerSearch.trainingEnabledbooleanfalse仅显示已培训讲者ACTIVE
speakerSearch.requiredNoticeEnabledbooleanfalse显示所需通知期ACTIVE
speakerSearch.unavailableDaysEnabledbooleanfalse显示不可用日ACTIVE
speakerSearch.otherLanguageEnabledbooleanfalse显示其他语言ACTIVE
speakerSearch.localHonorariaEnabledbooleanfalse显示本地酬金ACTIVE
speakerSearch.travelHonorariaEnabledbooleanfalse显示差旅酬金ACTIVE
speakerSearch.disableNotesbooleanfalse禁用备注ACTIVE
speakerSearch.enableSpeakerCategorybooleanfalse启用讲者类别ACTIVE
speakerSearch.enableSpeakerLevelbooleanfalse启用讲者等级ACTIVE

3.2.7 Product Level - Survey Configuration Flags

位于 modules/v1/config/SurveyConfiguration.java

Flag 名称类型默认值描述状态
survey.salesRepSurveyEnabledbooleanfalse启用销售代表问卷ACTIVE
survey.attendeeSurveyEnabledbooleantrue启用参会者问卷ACTIVE

3.2.8 Product Level - Alert Configuration Flags

位于 modules/v1/config/AlertConfiguration.java

Flag 名称类型默认值描述状态
alert.salesRepSurveyEnabledbooleanfalse启用问卷提醒ACTIVE

3.2.9 Product Level - Document Template Flags

位于 modules/v1/config/DocumentTemplateConfiguration.java + TemplateRestrictions.java

每个模板有 4 个子 flag: restricted, permitAll, enableDocx, enablePdf

模板类型描述状态
documentTemplate.signInSheetEnabled签到表模板ACTIVE
documentTemplate.paperInvitationEnabled纸质邀请模板ACTIVE
documentTemplate.attendeeSurveyResponsesEnabled参会者问卷回复模板ACTIVE
documentTemplate.signatureReportEnabled签名报告模板ACTIVE
documentTemplate.honorariaInvoiceEnabled酬金发票模板ACTIVE
documentTemplate.speakerExpenseReimbursementEnabled讲者费用报销模板ACTIVE
documentTemplate.postProgramCertificationEnabled项目后认证模板ACTIVE
documentTemplate.honorariaCoverLetterEnabled酬金附函模板ACTIVE

共计 8 个模板 x 4 个子 flag = 32 个文档模板 flag

3.2.10 Company Level Feature Flags (CompanyConfiguration)

位于 modules/v1/config/CompanyConfiguration.java

Flag 名称类型默认值描述状态
disableSurveyAlertsbooleanfalse禁用问卷提醒ACTIVE
speakerSurveyEnabledbooleantrue启用讲者问卷ACTIVE
disablePresentationDeletebooleanfalse禁止删除演示文稿ACTIVE
surveyReportbooleanfalse显示问卷报告ACTIVE
disableUserGroupbooleanfalse禁用用户组ACTIVE
enableEditProfilebooleanfalse启用编辑个人资料ACTIVE
enableSignUpbooleanfalse启用注册表单ACTIVE
enablePresentationMergeFieldbooleanfalse启用演示文稿合并字段ACTIVE
disablePresentationOnTrainingbooleanfalse培训页禁用演示文稿下载ACTIVE
enableContractAdvancedbooleanfalse启用合同高级功能ACTIVE
enableLEIECheckbooleanfalse启用 LEIE 检查ACTIVE

3.2.11 Company Level - Navigation Flags

位于 modules/v1/config/CompanyNavigation.java

Flag 名称类型默认值描述状态
navigation.disableTopicsbooleanfalse禁用主题标签页ACTIVE
navigation.disableTrainingbooleanfalse禁用培训标签页ACTIVE
navigation.disableExpenseReportsbooleanfalse禁用费用报告标签页ACTIVE
navigation.disableNominationsbooleanfalse禁用提名标签页ACTIVE
navigation.disableReportsbooleanfalse禁用报告标签页ACTIVE
navigation.disableScheduledProgramsbooleanfalse禁用已安排项目标签页ACTIVE
navigation.disableCompletedProgramsbooleanfalse禁用已完成项目标签页ACTIVE
navigation.enableAdminbooleanfalse启用管理员标签页ACTIVE

3.2.12 Database-level Configuration Flags (AttendanceConfig)

位于 common/persistence/entity/AttendanceConfig.java

Flag 名称类型默认值描述状态
topicEnabledBooleannull出席按主题统计ACTIVE
speakerEnabledBooleannull出席按讲者统计ACTIVE
prohibitTrainedSpeakerBooleannull禁止已培训讲者ACTIVE
frequencyCheckEnabledBooleannull启用频率检查ACTIVE

3.2.13 已移除的 Feature Flags (REMOVED)

根据 CLAUDE.md 记录,以下 flag 已从代码库中移除:

Flag 名称原位置移除原因状态
salesBudgetEnabledAgency/Product config功能废弃REMOVED
travelFormEnabledAgency/Product config功能废弃REMOVED
enableProgramObjectiveAgency/Product config功能废弃REMOVED
enableFeeForServiceContractsProduct configFee for Service 功能移除REMOVED
enableSpeakerCustomFieldsForLantheusProduct configLantheus 自定义字段移除REMOVED
enableActivitiesAgency configActivities 功能废弃REMOVED
enableContractTypeProduct configContract Type 移除REMOVED
enableMSANotificationProduct configMSA 通知移除REMOVED
onlineTrainingEnabledProduct config在线培训移除REMOVED
videoEnabledProduct config视频功能移除REMOVED
presentationEnabledProduct configPresentation Manager 移除REMOVED
enableOnlinePresentationProduct config在线演示移除REMOVED
ppt2Html5EnabledProduct configPPT 转 HTML5 移除REMOVED
patientAdvocateRequestedEnabledProgramFieldConfiguration字段移除REMOVED
speakersDisabledProgramFieldConfiguration字段移除REMOVED
requestUnitProgramFieldConfiguration字段移除REMOVED
requestUnitTimeProgramFieldConfiguration字段移除REMOVED
requestUnitPickupTimeProgramFieldConfiguration字段移除REMOVED

3.2.14 Feature Flags 统计汇总

类别数量
Agency Level Flags14
Product Level Flags (顶级)41
Program Configuration Flags9
Program List Flags7
Registration Flags19
Speaker Search Flags17
Survey Flags2
Alert Flags1
Document Template Flags32 (8 templates x 4 sub-flags)
Company Level Flags11
Company Navigation Flags8
Database Config Flags4
ACTIVE Total~165
REMOVED Flags18

3.3 Business Logic Issues

BL-1: 三层配置来源冲突

  • YAML 配置 (AgencyConfiguration) - 启动时加载,运行时不可变
  • 数据库 t_productproduct_config JSONB 字段 - 运行时可修改
  • 数据库 t_product_configuration 表 - 运行时可修改(注册状态、Logo)
  • 这三层配置的优先级关系在代码中没有明确定义

BL-2: FiscalPeriod 硬编码年份

  • 文件: modules/v1/config/FiscalPeriod.java:11-22
  • 选项列表硬编码到 2025 年,当前已是 2026 年,需要手动添加

BL-3: YAML 配置无法运行时修改

  • 所有 YAML flag 需要重启服务才能生效
  • 没有 Spring Cloud Bus 或 /actuator/refresh 端点来热更新
  • 客户需求变更时必须改 YAML 文件并重新部署

BL-4: enable*disable* 命名不一致

  • 正向开关: enableScheduledReports, enableApprovalFlow, enableSignUp
  • 反向开关: disableSurveyAlerts, disablePlannerMeetingReminder, disableChannel
  • 前端需要对正向和反向 flag 做不同的逻辑处理,容易出错

BL-5: PublicConfigurationController 暴露完整 AgencyConfiguration

  • 文件: modules/v1/config/controller/PublicConfigurationController.java:30-43
  • GET /v1/public/configuration 返回完整的 AgencyConfiguration,包括 SendGrid API Key, SFTP 密码等敏感信息
  • 虽然前端可能会过滤,但 API 层面存在信息泄露风险

BL-6: 产品级 vs 公司级配置边界模糊

  • speakerLevelsspecialities 同时出现在 CompanyConfigurationProductConfiguration
  • 不清楚哪一层的配置优先

4. API Inventory

4.1 REST Endpoints Table

4.1.1 Configuration API

MethodEndpoint认证Controller描述
GET/v1/configuration/instanceYesInstanceConfigurationController获取实例配置(Agency 级)
GET/v1/configuration/productsYesProductConfigurationController获取所有产品配置
GET/v1/configuration/products/{productId}YesProductConfigurationController获取单个产品配置
GET/v1/configuration/products/{productId}/speaker-searchYesProductConfigurationController获取讲者搜索配置
GET/v1/configuration/products/{productId}/programYesProductConfigurationController获取项目配置
GET/v1/configuration/products/{productId}/custom-labelYesProductConfigurationController获取自定义标签配置
GET/v1/configuration/products/{productId}/linksYesProductConfigurationController获取链接配置
GET/v1/public/configurationNoPublicConfigurationController获取完整公共配置(SalesView 用)
GET/v1/public/configuration/productNoPublicConfigurationController获取产品公共配置(通过 Header)
GET/v1/public/configuration/companyNoPublicConfigurationController获取公司配置(SpeakerView 用)

4.1.2 Product API

MethodEndpoint认证Controller描述
GET/v1/products/registrationstatusYesProductController获取所有产品注册状态
PUT/v1/products/registrationstatusYesProductController更新产品注册状态
GET/v1/products/{productId}/logosYesProductController获取产品 Logo
PUT/v1/products/{productId}/logosYesProductController保存 Logo
PUT/v1/products/{productId}/logos/{programTypeId}/enableYesProductController启用项目类型 Logo
PUT/v1/products/{productId}/logos/{programTypeId}/disableYesProductController禁用项目类型 Logo
GET/v1/products/{productId}/presentationsYesProductController获取演示文稿列表
GET/v1/products/{productId}/otherfilesYesProductController获取其他文件列表
GET/v1/products/{productId}/default-registration-statusYesProductController获取默认注册状态

4.1.3 Dictionary API

MethodEndpoint认证Controller描述
GET/v1/dictionary/productsYesDictionaryController产品字典
GET/v1/dictionary/companiesYesDictionaryController公司字典
GET/v1/dictionary/programtypesYesDictionaryController项目类型字典(含服务选项)
GET/v1/dictionary/servicetypesYesDictionaryController服务类型字典
GET/v1/dictionary/brandsYesDictionaryController品牌字典
GET/v1/dictionary/teamsYesDictionaryController团队字典
GET/v1/dictionary/salesforcesYesDictionaryController销售团队字典
GET/v1/dictionary/regionsYesDictionaryController区域字典
GET/v1/dictionary/districtsYesDictionaryController地区字典
GET/v1/dictionary/territoriesYesDictionaryController辖区字典
GET/v1/dictionary/speakersYesDictionaryController讲者字典
GET/v1/dictionary/salesYesDictionaryController销售代表字典
GET/v1/dictionary/district-managersYesDictionaryControllerDM 字典
GET/v1/dictionary/regional-managersYesDictionaryControllerRM 字典
GET/v1/dictionary/plannersYesDictionaryControllerPlanner 字典
GET/v1/dictionary/topicsYesDictionaryController主题字典
GET/v1/dictionary/venuesYesDictionaryController场地字典
GET/v1/dictionary/aggregate-spend-report-fieldsYesDictionaryController聚合支出报告字段
GET/v1/dictionary/plidsYesDictionaryControllerPLID 字典

4.1.4 CRUD Management API

MethodEndpoint认证Controller描述
GET/v1/brandsYesBrandController品牌列表(分页)
POST/v1/brandsYesBrandController创建品牌
PUT/v1/brands/{id}YesBrandController更新品牌
DELETE/v1/brands/{id}YesBrandController删除品牌
PUT/v1/brands/{id}/statusYesBrandController更新品牌状态
GET/v1/programtypesYesProgramTypeController项目类型列表
POST/v1/programtypesYesProgramTypeController创建项目类型
PUT/v1/programtypesYesProgramTypeController更新项目类型
PUT/v1/programtypes/{id}/statusYesProgramTypeController更新项目类型状态
PUT/v1/programtypes/plannersYesProgramTypeController分配 Planner
PUT/v1/programtypes/{programTypeId}/approvalsYesProgramTypeController保存审批配置
GET/v1/servicetypesYesServiceTypeController服务类型列表
PUT/v1/servicetypesYesServiceTypeController保存服务类型
PUT/v1/servicetypes/{serviceTypeId}/virtual-program-infoYesServiceTypeController更新虚拟项目信息
GET/v1/teamsYesTeamController团队列表
POST/v1/teamsYesTeamController创建团队
PUT/v1/teams/{id}YesTeamController更新团队
DELETE/v1/teams/{id}YesTeamController删除团队
PUT/v1/teams/{id}/statusYesTeamController更新团队状态
GET/v1/attendance/{programTypeId}YesAttendanceController获取出席配置
POST/v1/attendanceYesAttendanceController保存出席配置
GET/v1/preferred-speaker/{productId}YesPreferredSpeakerController获取首选讲者配置
POST/v1/preferred-speakerYesPreferredSpeakerController保存首选讲者配置
GET/v1/force-target-invitation/config/{teamId}YesForceTargetInvitationController获取强制目标邀请配置
POST/v1/force-target-invitation/configYesForceTargetInvitationController保存强制目标邀请配置
GET/v1/force-target-invitation/targetsYesForceTargetInvitationController获取强制邀请目标列表

4.2 API Design Issues

API-1: RESTful 命名不规范

  • GET /v1/products/registrationstatus - 缺少连字符,应为 registration-status
  • GET /v1/dictionary/programtypes - 应为 program-types
  • GET /v1/dictionary/servicetypes - 应为 service-types
  • GET /v1/dictionary/salesforces - 语义不清(实际是 Sales Team/Force)

API-2: PUT 用于创建操作

  • PUT /v1/programtypes 同时用于创建和更新,违反 REST 语义
  • PUT /v1/servicetypes 用于批量保存,不符合 REST 规范

API-3: PublicConfigurationController 安全风险

  • GET /v1/public/configuration 无需认证但返回包含 SendGrid API Key 的完整 AgencyConfiguration
  • 文件: modules/v1/config/controller/PublicConfigurationController.java:30-43
  • AgencyConfiguration 包含 sendGrid.apiKey, sftp.password 等敏感字段

API-4: 产品 ID 通过 Header 传递

  • GET /v1/public/configuration/product 通过 Pharmagin-Product-ID Header 获取产品 ID
  • 文件: modules/v1/config/controller/PublicConfigurationController.java:47
  • 不符合 REST 惯例,应通过路径参数传递

API-5: 字典 API 与 CRUD API 重复

  • GET /v1/dictionary/brandsGET /v1/brands 返回类似数据
  • GET /v1/dictionary/teamsGET /v1/teams 返回类似数据
  • GET /v1/dictionary/programtypesGET /v1/programtypes 返回类似数据
  • 区别仅在于字典 API 返回精简的下拉列表数据,CRUD API 返回完整管理数据

API-6: Region/District/Territory Controller 几乎为空

  • RegionController (line 11-13) 继承 BaseController 但没有任何端点
  • CRUD 操作可能分散在其他地方或通过直接数据库操作

5. Frontend Analysis

5.1 Pages & Components

5.1.1 PlannerView Admin Page

位于 pharmagin-plannerview/legacy/src/containers/AdminPage/

Admin 页面包含两个主要的功能分组和 23 个管理子页面:

Program-related 分组:

菜单项组件文件路径条件
PlannerView UsersUsercomponents/User/PROGRAM_ADMIN
Push AlertsPushAlertcomponents/PushAlert/PROGRAM_ADMIN
Budget CategoriesBudgetCategoriescomponents/BudgetCategories/PROGRAM_ADMIN
Program TypesProgramTypecomponents/ProgramType/PROGRAM_ADMIN
Budget TemplatesBudgetTemplatecomponents/BudgetTemplate/PROGRAM_ADMIN
BrandBrandcomponents/Brand/PROGRAM_ADMIN
Setup Sales ForceSalesForcecomponents/SalesForce/PROGRAM_ADMIN
Setup TeamTeamcomponents/Team/PROGRAM_ADMIN
Attendance FrequencyAttendanceFrequencycomponents/AttendanceFrequency/PROGRAM_ADMIN
Registration WorkflowRegistrationWorkflowcomponents/RegistrationWorkflow/PROGRAM_ADMIN
Force Target InvitationForceTargetInvitationcomponents/ForceTargetInvitation/PROGRAM_ADMIN + enableForceTargetInvitation flag
Email Invitation TemplatesEmailInvitationTemplatescomponents/EmailInvitationTemplates/PROGRAM_ADMIN
Email SandboxEmailSandboxcomponents/EmailSandbox/PROGRAM_ADMIN
Document TemplatesTemplatecomponents/Template/PROGRAM_ADMIN + DOCUMENT_TEMPLATE_ADMIN
Compliance Document ChecklistDocumentChecklistcontainers/Compliance/DocumentChecklist/PROGRAM_ADMIN + AUDIT
Preferred Speakers ListPreferredSpeakersListcomponents/PreferredSpeakersList/PROGRAM_ADMIN + enablePreferredSpeakersList flag
Configure Program Task TemplatesProjectTaskcomponents/ProjectTask/PROGRAM_ADMIN + projectTaskEnabled flag
Configure Aggregate Spend ReportsAggregateSpendReportConfigurationcomponents/AggregateSpendReportConfiguration/PROGRAM_ADMIN
LogosUploadLogoscomponents/UploadLogos/无条件
SurveysSurveyscomponents/Surveys/SURVEY_ADMIN
Unsubscribed ListUnsubscribedListcomponents/UnsubscribedList/enableUnsubscribedEmail flag
SFTPSFTPcomponents/SFTP/无条件

Advanced 分组:

菜单项组件文件路径条件
Multi-Module UsersPharmaginUsercomponents/PharmaginUser/MULTI_MODULE_USER_ADMIN
User RolesPharmaginRolecomponents/PharmaginRole/MULTI_MODULE_USER_ADMIN
Users LimitUsersLimitcomponents/UsersLimit/MULTI_MODULE_USER_ADMIN
Download Change LogDownloadChangeLogcontainers/AdminPage/DownloadChangeLog/DOWNLOAD_PROGRAM_CHANGE_LOG

5.1.2 SalesView Configuration Usage

SalesView 在启动时获取配置并存储到 Redux:

  • GET /api/v1/public/configuration/product - 获取产品配置
  • 如果 switchProductEnabled 为 true,还会获取 GET /api/v1/public/configuration

5.1.3 SpeakerView Configuration Usage

SpeakerView 在启动时获取公司配置:

  • GET /api/v1/public/configuration/company - 获取公司配置
  • 存储到 sessionStorage 和 Redux

5.2 Redux State Structure

PlannerView

javascript
// State shape: global.instanceConfig
{
  name: String,                          // Agency name
  url: String,                           // Agency URL
  products: {                            // Map<productId, ProductConfiguration>
    [productId]: {
      salesViewUrl: String,
      reconciliationType: Number,
      customLabel: { ... },
      speakerSearch: { ... },
      program: { ... },
      registration: { ... },
      survey: { ... },
      documentTemplate: { ... },
      // ... 100+ feature flags
    }
  },
  projectTaskEnabled: Boolean,
  enableScheduledReports: Boolean,
  enableApprovalFlow: Boolean,
  enableAutomatedEmail: Boolean,
  enablePreferredSpeakersList: Boolean,
  enableForceTargetInvitation: Boolean,
  enableHonorariaAmount: Boolean,
  enableCloseOutProgram: Boolean,
  enableUnsubscribedEmail: Boolean,
  enableDataExceptions: Boolean,
}

Selectors (文件: containers/App/selectors.js):

  • selectInstanceConfig() - 获取完整 instanceConfig
  • selectProductConfig(productId) - 获取特定产品配置

SalesView

javascript
// State shape: app.globalConfig (ProductConfiguration)
{
  salesViewUrl: String,
  reconciliationType: Number,
  switchProductEnabled: Boolean,
  program: { ... },
  registration: { ... },
  speakerSearch: { ... },
  // ... all ProductConfiguration fields
}

SpeakerView

javascript
// State shape via sessionStorage: companyConfiguration (CompanyConfiguration)
{
  url: String,
  companyName: String,
  logo: String,
  theme: { ... },
  navigation: { ... },
  enableEditProfile: Boolean,
  enableSignUp: Boolean,
  // ... all CompanyConfiguration fields
}

5.3 Frontend Issues

FE-1: 配置数据重复存储

  • PlannerView 将配置同时存储在 Redux (instanceConfig) 和 sessionStorage (projectConfig)
  • SpeakerView 将配置同时存储在 Redux 和 sessionStorage (companyConfiguration)
  • 两份数据可能不同步

FE-2: 无类型检查

  • 前端使用原生 JavaScript,配置对象没有 TypeScript 类型定义
  • 100+ 个 feature flag 的拼写错误或缺失不会被编译期检测

FE-3: 配置传递方式混乱

  • 部分组件通过 Redux selector 获取配置
  • 部分组件从 sessionStorage 读取配置
  • 部分组件通过 props 接收配置
  • 无统一的配置访问模式

FE-4: Admin 页面权限粒度不够

  • 大部分菜单项仅需 PROGRAM_ADMIN 权限即可访问
  • 没有更细粒度的权限控制(如只允许管理 Brand 不允许管理 ProgramType)

6. Problem Summary

6.1 Critical Issues (must fix in rewrite)

ID问题影响范围详情
C-1公共 API 泄露敏感配置安全PublicConfigurationController.getInstanceConfiguration() 返回完整 AgencyConfiguration,包含 SendGrid API Key、SFTP 密码、Google API Key 等。文件: PublicConfigurationController.java:30-43
C-2三重配置来源无优先级架构YAML (AgencyConfiguration), DB (t_product.product_config JSONB), DB (t_product_configuration) 三个配置来源的优先级关系未定义,可能导致配置冲突
C-3165+ Feature Flags 全部嵌入 YAML运维所有 feature flag 需要修改 YAML 并重启服务才能生效,无法运行时动态切换。任何客户的功能启停都需要重新部署
C-4ProductConfiguration 同名类冲突代码可维护性common.persistence.entity.ProductConfiguration (DB entity, 3 fields) 与 modules.v1.config.ProductConfiguration (YAML POJO, 100+ fields) 完全不同但同名
C-5FiscalPeriod 硬编码到 2025功能缺陷FiscalPeriod.java:11-22 硬编码年份列表到 2025,2026 年开始功能失效

6.2 Design Defects (should improve)

ID问题影响范围详情
D-1Feature Flag 命名不一致开发体验enable*disable* 混用,如 enableScheduledReports vs disableSurveyAlerts,易导致前端逻辑错误
D-2配置层级重叠业务逻辑speakerLevelsspecialities 同时出现在 CompanyConfiguration 和 ProductConfiguration,优先级不明
D-3字典 API 与 CRUD API 重复API 设计GET /v1/dictionary/brandsGET /v1/brands 返回类似数据,增加维护成本
D-4JSONB 字段无 Schema 验证数据完整性Product.productConfig, ProductConfiguration.logos, ProgramServiceType.spaceConfig 等 JSONB 字段类型为 Object,无结构约束
D-5Region/State/District 无 CRUD API功能缺失RegionController 为空壳,地理数据无标准管理接口
D-6软删除与硬删除混用数据一致性BrandService.deleteBrand() 先设 status=0 再调用 delete(),逻辑矛盾
D-7产品 ID 通过 HTTP Header 传递REST 规范PublicConfigurationController.findProductConfiguration() 通过 Pharmagin-Product-ID Header 获取产品 ID,不符合 REST 惯例
D-8DynamicProgramField 过度灵活前端复杂度requiredvisible 字段类型为 Object,可以是布尔值也可以是表达式字符串(如 "{ data.fieldExhibit === true ? { display: 'none' } : '' }"),前端需要 eval 处理

6.3 Technical Debt (nice to have)

ID问题影响范围详情
T-1Topic 实体未使用 Lombok代码风格Topic.java 手写 getter/setter 140+ 行,与其他实体不一致
T-2Team 冗余外键数据模型Team 同时有 salesTeamIdsalesForceId,语义重叠
T-3前端无 TypeScript 类型开发效率100+ feature flags 无编译时类型检查
T-4配置双重存储前端架构PlannerView/SpeakerView 在 Redux 和 sessionStorage 中双重存储配置
T-5REST URL 命名不规范API 规范registrationstatus, programtypes, servicetypes 缺少连字符
T-6PUT 用于创建REST 规范PUT /v1/programtypes 同时处理创建和更新
T-7无配置变更审计合规YAML 配置变更无审计日志(数据库变更通过 JaVers 有审计)
T-8VirtualProgramConfig 包含 Zoom 凭证安全VirtualProgramConfig.java 包含 s2sClientSecret, meetingSDKClientSecret 等敏感凭证直接存储在 YAML 中

7. Rewrite Recommendations

7.1 配置系统重构

R-1: 统一配置存储到数据库

  • 将所有 feature flags 从 YAML 迁移到数据库表
  • 设计 t_feature_flag 表: (flag_key, scope, scope_id, value, description, updated_at, updated_by)
  • scope 枚举: AGENCY, PRODUCT, COMPANY
  • 提供管理 API 进行运行时动态切换
  • 保留 YAML 仅用于基础设施配置(数据库连接、端口、外部服务 URL 等)

R-2: 消除 ProductConfiguration 命名冲突

  • 将 DB entity 重命名为 ProductDbConfiguration 或合并到 Product 实体
  • YAML POJO 可保留 ProductConfiguration 名称但标记为内部类

R-3: 统一 Feature Flag 命名规范

  • 全部使用正向命名: xxxEnabled (boolean, true = enabled)
  • 对于反向含义的: surveyAlertsEnabled 替代 disableSurveyAlerts

7.2 API 重构

R-4: 分离公共/管理 API,保护敏感数据

  • 公共 API 只返回前端需要的配置子集(排除 API keys, passwords)
  • 创建专用的 PublicProductConfig DTO,而不是直接返回 AgencyConfiguration

R-5: 合并字典和 CRUD API

  • 使用查询参数区分完整数据和精简数据: GET /v1/brands?view=dictionary vs GET /v1/brands?view=admin
  • 或使用 GraphQL 让客户端指定需要的字段

R-6: 修复 REST 规范问题

  • 使用 kebab-case: registration-status, program-types, service-types
  • 产品 ID 通过路径参数传递

7.3 数据模型改进

R-7: JSONB 字段类型化

  • Product.productConfig, ProductConfiguration.logos 等 JSONB 字段定义明确的 Java 类型
  • 使用 JSON Schema 或 Java 类型映射验证结构

R-8: 动态年份生成

  • FiscalPeriod 应该动态生成年份列表(当前年份 -5 到 +2),而非硬编码

R-9: 统一软删除策略

  • 所有实体使用统一的 status 字段软删除
  • 移除 BrandService.deleteBrand() 中的硬删除调用

7.4 前端改进

R-10: TypeScript 类型定义

  • 为所有配置对象生成 TypeScript 接口定义
  • 可从后端 Java 类自动生成

R-11: 统一配置访问层

  • 创建 ConfigurationContext (React Context) 作为唯一配置入口
  • 移除 sessionStorage 中的配置副本
  • 提供 useFeatureFlag('enablePLID') 等 hooks

R-12: 配置变更审计

  • 在配置管理 API 中加入审计日志
  • 记录谁在什么时候修改了什么配置