Configuration & Features Domain - Deep Dive Analysis
1. Domain Overview
1.1 领域职责描述
Configuration & Features 领域是整个 Speaker Platform 的"控制中枢",负责管理平台的所有可配置行为。该领域承担以下核心职责:
- 实例级配置管理 (Agency Configuration) - 通过 Spring Cloud Config (YAML) 管理每个客户部署实例的全局配置
- 产品级配置管理 (Product Configuration) - 每个药企客户的产品线独立配置,包含 100+ 个功能开关
- 公司级配置管理 (Company Configuration) - 面向 Speaker 的品牌定制(logo、主题色、导航栏等)
- 字典数据管理 (Dictionary) - 业务下拉列表数据(Product、Brand、Topic、ProgramType、Region 等)
- 功能开关系统 (Feature Flags) - 控制功能模块的启用/禁用
- UI 标签定制 (Custom Labels) - 允许客户自定义 UI 标签文案
- 主题/品牌系统 (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-repo | pharmagin-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)
| Entity | Table | 主键 | 关键字段 | 文件位置 |
|---|---|---|---|---|
Product | t_product | product_id (sequence: s_product) | productName, company, companyId, logoPath, meetingSequence, crossContext, npiTarget, speakerLevel, districtOrder, districtSwitch, regActiveStatus, approvalCopyRm, salesforceUsername, salesforceSecurityToken, salesTeam, productConfig(JSONB), plidSequence | common/persistence/entity/Product.java |
ProductConfiguration | t_product_configuration | product_id | registrationStatus, logos(JSONB) | common/persistence/entity/ProductConfiguration.java |
Brand | t_brand | brand_id (sequence: s_brand) | brandName, productId, status | common/persistence/entity/Brand.java |
Topic | t_topic | topic_id (sequence: s_topic) | topicName, topicRef, topicDescription, productId, meetingType, serviceType, status, topicCode, shortName | common/persistence/entity/Topic.java |
Theme | t_theme | id (sequence: t_theme_id_seq) | name, content(JSONB), createdAt, createdBy, updatedAt, updatedBy | common/persistence/entity/Theme.java |
MeetingProgramType | t_meeting_program_type | program_type_id (sequence: s_program) | programTypeName, description, productId, meetingIdPrefix, projectCode, year, needApproval, meetingId, signatureText, status, sequence, brandId, userId, approvals(JSONB), color, fieldExhibit | common/persistence/entity/MeetingProgramType.java |
ProgramServiceType | t_meeting_program_service_type | program_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), hybrid | common/persistence/entity/ProgramServiceType.java |
Team | t_team | team_id (sequence: s_team) | teamName, productId, status, salesTeamId, salesForceId | common/persistence/entity/Team.java |
TeamBrand | (关联表) | - | teamId, brandId | common/persistence/entity/TeamBrand.java |
TeamProgramType | (关联表) | - | teamId, programTypeId | common/persistence/entity/TeamProgramType.java |
SalesTeam | t_sales_team | sales_team_id (sequence: s_sales_team) | salesTeamName, productId, status, speakerProgramReportDisabled | common/persistence/entity/SalesTeam.java |
Region | t_region | region_id (sequence: s_region) | regionName, productId, salesTeamId, status | common/persistence/entity/Region.java |
AttendanceConfig | t_attendance_config | program_type_id | topicEnabled, speakerEnabled, allowedAttendCount, frequencyPeriod, prohibitTrainedSpeaker, frequencyCheckEnabled, frequencyPeriodType, createdAt/By, updatedAt/By | common/persistence/entity/AttendanceConfig.java |
ProductRole | t_product_role | role_id | productId, roleName, displayName | common/persistence/entity/ProductRole.java |
ProductRolePermission | (关联表) | - | roleId, permissionId | common/persistence/entity/ProductRolePermission.java |
2.1.2 YAML 配置模型 (Spring Cloud Config POJOs)
| 配置类 | 配置前缀 | 关键属性 | 文件位置 |
|---|---|---|---|
AgencyConfiguration | agency | name, 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, google | modules/v1/config/AgencyConfiguration.java |
CompanyConfiguration | agency.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, dynamicContractFields | modules/v1/config/CompanyConfiguration.java |
ProductConfiguration | agency.products.{id} | (见下方 Feature Flags 完整清单) | modules/v1/config/ProductConfiguration.java |
ProgramConfiguration | agency.products.{id}.program | editProgram, editProgramOnlyApproved, approvalEnabled, label, field, serviceOptions, thanksMessage, attachmentsEnabled, shareProgramEnabled, cancelProgramEnabled, list, enableApproverComment, dynamicProgramFields, lunchAndLearnProgram, enableProgramPresentations, disabledProgramTimeOffset, programDuration | modules/v1/config/ProgramConfiguration.java |
RegistrationConfiguration | agency.products.{id}.registration | openRegistrationEnabled, registrationSiteEnabled, closeAttendeeListEnabled, closeAttendeeListPendingStatusCheckEnabled, copyAttendeesEnabled, updateCRMAttendeesEnabled, setRegistrationStatusEnabled, unreconcileEnabled, deleteAttendeeEnabled, addToSalesforceEnabled, enableEmailInvitation, allowSalesRepChangeEmailAlias, useDefaultStatusAsNextStatusForInvitedStatus, enableRegistrationSiteStatus, enableLookUpNPI, enableBatchRegistrationBasedOnAffiliation, enableBatchRegistration, enableCustomizedBrandPrefix, disableQuickRegistration | modules/v1/config/RegistrationConfiguration.java |
SpeakerSearchConfiguration | agency.products.{id}.speakerSearch | filterEnabled, stateEnabled, specialityEnabled, regionReadOnly, honorariaCapEnabled, availableAmountEnabled, continueWithoutSpeakerEnabled, ratingEnabled, trainingEnabled, requiredNoticeEnabled, unavailableDaysEnabled, otherLanguageEnabled, localHonorariaEnabled, travelHonorariaEnabled, cvConfirmMessage, disableNotes, enableSpeakerCategory, enableSpeakerLevel | modules/v1/config/SpeakerSearchConfiguration.java |
VirtualProgramConfig | agency.products.{id}.virtualPrograms.{key} | s2sOAuthTokenUrl, s2sAccountId, s2sClientId, s2sClientSecret, meetingSDKClientId, meetingSDKClientSecret, eventSecretToken, url, users, programSettings, polls, name, disableResendInvite, disableManualAttendeeAdd | modules/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 spaceConfig和virtualProgramInfo均为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 - 同时有
salesTeamId和salesForceId两个字段,语义重叠
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.java 和 pharmagin-config-repo/agency-dev.yml
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
projectTaskEnabled | boolean | false | 启用项目任务功能 | ACTIVE |
enableScheduledReports | boolean | false | 启用定时报告功能 | ACTIVE |
enableApprovalFlow | boolean | false | 启用可配置审批流 | ACTIVE |
enableAutomatedEmail | boolean | false | 启用自动化邮件 | ACTIVE |
enablePreferredSpeakersList | boolean | false | 启用首选讲者列表 | ACTIVE |
enableForceTargetInvitation | boolean | false | 启用强制目标邀请 | ACTIVE |
enableHonorariaAmount | boolean | false | 启用酬金金额设置 | ACTIVE |
enableCloseOutProgram | boolean | false | 启用关闭项目功能 | ACTIVE |
enableUnsubscribedEmail | boolean | false | 启用退订邮件管理 | ACTIVE |
enableDataExceptions | boolean | false | 启用数据异常追踪 | ACTIVE |
enableAttendeeTrackerDataToSftp | boolean | false | 启用参会者数据 SFTP 传输 | ACTIVE |
enableMosaicIntegration | boolean | false | 启用 Mosaic 集成 | ACTIVE |
enablePorzioAggSpendReport | boolean | false | 启用 Porzio 聚合支出报告 | ACTIVE |
enableGoogleGeocodeAPI | boolean | false | 启用 Google 地理编码 API | ACTIVE |
3.2.2 Product Level Feature Flags (ProductConfiguration)
位于 modules/v1/config/ProductConfiguration.java 和 pharmagin-config-repo/product-dev.yml
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
plannerIdFromProgramType | boolean | false | 按项目类型分配 Planner(非地理) | ACTIVE |
brandBudgetAllocationEnabled | boolean | false | 启用按品牌预算分配 | ACTIVE |
disallowProgramIfBudgetReached | boolean | false | 预算达限时禁止创建项目 | ACTIVE |
disallowProgramSubmission | boolean | false | 禁止项目提交 | ACTIVE |
switchProductEnabled | boolean | false | 启用产品切换功能 | ACTIVE |
presentationsEnabled | boolean | false | 启用演示文稿功能 | ACTIVE |
disablePlannerMeetingReminder | boolean | false | 禁用 Planner 会议提醒 | ACTIVE |
disableRMMeetingReminder | boolean | false | 禁用区域经理会议提醒 | ACTIVE |
kclMeetingReminderEnabled | boolean | false | 启用 KCL 会议提醒 | ACTIVE |
attendeeType2Enabled | boolean | false | 启用 Type2 参会者 | ACTIVE |
programSummaryReportRestrictionsEnabled | boolean | false | 启用项目摘要报告限制 | ACTIVE |
speakerUtilizationRestrictionsEnabled | boolean | false | 启用讲者利用率报告限制 | ACTIVE |
programSummaryByBudgetCategoryEnabled | boolean | false | 按预算类别生成项目摘要报告 | ACTIVE |
programSummaryCustomFieldsEnabled | boolean | false | 项目摘要报告自定义字段 | ACTIVE |
detailedProgramCountsEnabled | boolean | false | 讲者利用率报告详细计数 | ACTIVE |
simplifiedSpeakerListEnabled | boolean | false | 简化讲者列表下载 | ACTIVE |
disableLocalAdBoardTravelHonoraria | boolean | false | 禁用 Local Ad Board 差旅酬金 | ACTIVE |
enableNewSurvey | boolean | false | 启用新版问卷功能 | ACTIVE |
restrictCalendarViewByUser | boolean | false | 按用户限制日历视图 | ACTIVE |
enableCustomReport | boolean | false | 启用自定义报告 | ACTIVE |
enableSalesRepChat | boolean | false | 启用销售代表聊天 | ACTIVE |
disableChannel | boolean | false | 禁用频道功能 | ACTIVE |
enableGroupDiscussion | boolean | false | 启用群组讨论 | ACTIVE |
disableChatAcrossAttendees | boolean | false | 禁止跨参会者聊天 | ACTIVE |
enableSpeakerNomination | boolean | false | 启用讲者提名 | ACTIVE |
enableVideoCall | boolean | false | 启用音视频通话 | ACTIVE |
enableDownloadPVLink | boolean | false | 启用 PV 下载链接 | ACTIVE |
disableDownloadSpeakerListLink | boolean | false | 禁用下载讲者列表 | ACTIVE |
enablePLID | boolean | false | 启用 PLID/UUID 功能 | ACTIVE |
enableTOVCalculationNotification | boolean | false | 启用 TOV 计算邮件通知 | ACTIVE |
disableCalendarDescription | boolean | false | 禁用日历描述 | ACTIVE |
enableSpeakerAutomaticRegistrationCheck | boolean | false | 启用讲者自动注册检查 | ACTIVE |
enableScheduledReport | boolean | false | 启用定时报告(产品级) | ACTIVE |
enableSignInAttendees | boolean | false | 启用签到参会者功能 | ACTIVE |
enableQuickEmailInvitation | boolean | false | 启用快速邮件邀请 | ACTIVE |
enableMultipleEmailInvitation | boolean | false | 启用批量邮件邀请 | ACTIVE |
enableApprovalStatusOnProgramList | boolean | false | 项目列表显示审批状态 | ACTIVE |
enableAttendanceFormat | boolean | false | 启用出席格式 | ACTIVE |
disablePresentationOnResource | boolean | false | 资源页禁用演示文稿 | ACTIVE |
hideSupportText | boolean | false | 隐藏支持信息文本 | ACTIVE |
enableSpeakerRandomization | boolean | false | 启用讲者随机化 | ACTIVE |
3.2.3 Product Level - Program Configuration Flags
位于 modules/v1/config/ProgramConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
program.editProgram | boolean | false | 允许编辑项目 | ACTIVE |
program.editProgramOnlyApproved | boolean | false | 仅允许编辑已批准项目 | ACTIVE |
program.approvalEnabled | boolean | false | 启用项目审批 | ACTIVE |
program.attachmentsEnabled | boolean | false | 启用项目附件 | ACTIVE |
program.shareProgramEnabled | boolean | false | 启用项目共享 | ACTIVE |
program.cancelProgramEnabled | boolean | false | 启用项目取消 | ACTIVE |
program.enableApproverComment | boolean | false | 启用审批者评论 | ACTIVE |
program.lunchAndLearnProgram | boolean | false | 启用午餐学习项目检查 | ACTIVE |
program.enableProgramPresentations | boolean | false | 启用项目演示文稿 | ACTIVE |
3.2.4 Product Level - Program List Configuration Flags
位于 modules/v1/config/ProgramListConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
program.list.brandEnabled | boolean | true | 项目列表品牌过滤 | ACTIVE |
program.list.topicEnabled | boolean | true | 项目列表主题过滤 | ACTIVE |
program.list.teamEnabled | boolean | false | 项目列表团队过滤 | ACTIVE |
program.list.venueEnabled | boolean | false | 项目列表场地过滤 | ACTIVE |
program.list.locationEnabled | boolean | false | 项目列表地点过滤 | ACTIVE |
program.list.districtManagerEnabled | boolean | false | 项目列表 DM 过滤 | ACTIVE |
program.list.regionalManagerEnabled | boolean | false | 项目列表 RM 过滤 | ACTIVE |
3.2.5 Product Level - Registration Configuration Flags
位于 modules/v1/config/RegistrationConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
registration.openRegistrationEnabled | boolean | false | 启用开放注册 | ACTIVE |
registration.registrationSiteEnabled | boolean | false | 启用注册站点链接 | ACTIVE |
registration.closeAttendeeListEnabled | boolean | false | 启用关闭参会者列表 | ACTIVE |
registration.closeAttendeeListPendingStatusCheckEnabled | boolean | false | 关闭时检查待定状态 | ACTIVE |
registration.copyAttendeesEnabled | boolean | false | 启用复制参会者 | ACTIVE |
registration.updateCRMAttendeesEnabled | boolean | false | 启用更新 CRM 参会者 | ACTIVE |
registration.setRegistrationStatusEnabled | boolean | false | 启用设置注册状态 | ACTIVE |
registration.unreconcileEnabled | boolean | false | 启用反核销 | ACTIVE |
registration.deleteAttendeeEnabled | boolean | false | 启用删除参会者 | ACTIVE |
registration.addToSalesforceEnabled | boolean | false | 启用添加到 Salesforce | ACTIVE |
registration.enableEmailInvitation | boolean | false | 启用邮件邀请 | ACTIVE |
registration.allowSalesRepChangeEmailAlias | boolean | false | 允许销售代表修改邮件别名 | ACTIVE |
registration.useDefaultStatusAsNextStatusForInvitedStatus | boolean | false | 将默认状态作为邀请状态的下一状态 | ACTIVE |
registration.enableRegistrationSiteStatus | boolean | false | 启用注册站点状态显示 | ACTIVE |
registration.enableLookUpNPI | boolean | false | 启用 NPI 查询 | ACTIVE |
registration.enableBatchRegistrationBasedOnAffiliation | boolean | false | 启用基于从属关系的批量注册 | ACTIVE |
registration.enableBatchRegistration | boolean | false | 启用批量注册 | ACTIVE |
registration.enableCustomizedBrandPrefix | boolean | false | 启用自定义品牌前缀 | ACTIVE |
registration.disableQuickRegistration | boolean | false | 禁用快速注册 | ACTIVE |
3.2.6 Product Level - Speaker Search Configuration Flags
位于 modules/v1/config/SpeakerSearchConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
speakerSearch.filterEnabled | boolean | false | 启用讲者组过滤 | ACTIVE |
speakerSearch.stateEnabled | boolean | false | 启用州过滤 | ACTIVE |
speakerSearch.specialityEnabled | boolean | false | 启用专科过滤 | ACTIVE |
speakerSearch.regionReadOnly | boolean | false | 区域只读 | ACTIVE |
speakerSearch.honorariaCapEnabled | boolean | false | 显示酬金上限状态 | ACTIVE |
speakerSearch.availableAmountEnabled | boolean | false | 显示可用酬金金额 | ACTIVE |
speakerSearch.continueWithoutSpeakerEnabled | boolean | false | 允许无讲者继续 | ACTIVE |
speakerSearch.ratingEnabled | boolean | false | 启用讲者评分 | ACTIVE |
speakerSearch.trainingEnabled | boolean | false | 仅显示已培训讲者 | ACTIVE |
speakerSearch.requiredNoticeEnabled | boolean | false | 显示所需通知期 | ACTIVE |
speakerSearch.unavailableDaysEnabled | boolean | false | 显示不可用日 | ACTIVE |
speakerSearch.otherLanguageEnabled | boolean | false | 显示其他语言 | ACTIVE |
speakerSearch.localHonorariaEnabled | boolean | false | 显示本地酬金 | ACTIVE |
speakerSearch.travelHonorariaEnabled | boolean | false | 显示差旅酬金 | ACTIVE |
speakerSearch.disableNotes | boolean | false | 禁用备注 | ACTIVE |
speakerSearch.enableSpeakerCategory | boolean | false | 启用讲者类别 | ACTIVE |
speakerSearch.enableSpeakerLevel | boolean | false | 启用讲者等级 | ACTIVE |
3.2.7 Product Level - Survey Configuration Flags
位于 modules/v1/config/SurveyConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
survey.salesRepSurveyEnabled | boolean | false | 启用销售代表问卷 | ACTIVE |
survey.attendeeSurveyEnabled | boolean | true | 启用参会者问卷 | ACTIVE |
3.2.8 Product Level - Alert Configuration Flags
位于 modules/v1/config/AlertConfiguration.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
alert.salesRepSurveyEnabled | boolean | false | 启用问卷提醒 | 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 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
disableSurveyAlerts | boolean | false | 禁用问卷提醒 | ACTIVE |
speakerSurveyEnabled | boolean | true | 启用讲者问卷 | ACTIVE |
disablePresentationDelete | boolean | false | 禁止删除演示文稿 | ACTIVE |
surveyReport | boolean | false | 显示问卷报告 | ACTIVE |
disableUserGroup | boolean | false | 禁用用户组 | ACTIVE |
enableEditProfile | boolean | false | 启用编辑个人资料 | ACTIVE |
enableSignUp | boolean | false | 启用注册表单 | ACTIVE |
enablePresentationMergeField | boolean | false | 启用演示文稿合并字段 | ACTIVE |
disablePresentationOnTraining | boolean | false | 培训页禁用演示文稿下载 | ACTIVE |
enableContractAdvanced | boolean | false | 启用合同高级功能 | ACTIVE |
enableLEIECheck | boolean | false | 启用 LEIE 检查 | ACTIVE |
3.2.11 Company Level - Navigation Flags
位于 modules/v1/config/CompanyNavigation.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
navigation.disableTopics | boolean | false | 禁用主题标签页 | ACTIVE |
navigation.disableTraining | boolean | false | 禁用培训标签页 | ACTIVE |
navigation.disableExpenseReports | boolean | false | 禁用费用报告标签页 | ACTIVE |
navigation.disableNominations | boolean | false | 禁用提名标签页 | ACTIVE |
navigation.disableReports | boolean | false | 禁用报告标签页 | ACTIVE |
navigation.disableScheduledPrograms | boolean | false | 禁用已安排项目标签页 | ACTIVE |
navigation.disableCompletedPrograms | boolean | false | 禁用已完成项目标签页 | ACTIVE |
navigation.enableAdmin | boolean | false | 启用管理员标签页 | ACTIVE |
3.2.12 Database-level Configuration Flags (AttendanceConfig)
位于 common/persistence/entity/AttendanceConfig.java
| Flag 名称 | 类型 | 默认值 | 描述 | 状态 |
|---|---|---|---|---|
topicEnabled | Boolean | null | 出席按主题统计 | ACTIVE |
speakerEnabled | Boolean | null | 出席按讲者统计 | ACTIVE |
prohibitTrainedSpeaker | Boolean | null | 禁止已培训讲者 | ACTIVE |
frequencyCheckEnabled | Boolean | null | 启用频率检查 | ACTIVE |
3.2.13 已移除的 Feature Flags (REMOVED)
根据 CLAUDE.md 记录,以下 flag 已从代码库中移除:
| Flag 名称 | 原位置 | 移除原因 | 状态 |
|---|---|---|---|
salesBudgetEnabled | Agency/Product config | 功能废弃 | REMOVED |
travelFormEnabled | Agency/Product config | 功能废弃 | REMOVED |
enableProgramObjective | Agency/Product config | 功能废弃 | REMOVED |
enableFeeForServiceContracts | Product config | Fee for Service 功能移除 | REMOVED |
enableSpeakerCustomFieldsForLantheus | Product config | Lantheus 自定义字段移除 | REMOVED |
enableActivities | Agency config | Activities 功能废弃 | REMOVED |
enableContractType | Product config | Contract Type 移除 | REMOVED |
enableMSANotification | Product config | MSA 通知移除 | REMOVED |
onlineTrainingEnabled | Product config | 在线培训移除 | REMOVED |
videoEnabled | Product config | 视频功能移除 | REMOVED |
presentationEnabled | Product config | Presentation Manager 移除 | REMOVED |
enableOnlinePresentation | Product config | 在线演示移除 | REMOVED |
ppt2Html5Enabled | Product config | PPT 转 HTML5 移除 | REMOVED |
patientAdvocateRequestedEnabled | ProgramFieldConfiguration | 字段移除 | REMOVED |
speakersDisabled | ProgramFieldConfiguration | 字段移除 | REMOVED |
requestUnit | ProgramFieldConfiguration | 字段移除 | REMOVED |
requestUnitTime | ProgramFieldConfiguration | 字段移除 | REMOVED |
requestUnitPickupTime | ProgramFieldConfiguration | 字段移除 | REMOVED |
3.2.14 Feature Flags 统计汇总
| 类别 | 数量 |
|---|---|
| Agency Level Flags | 14 |
| Product Level Flags (顶级) | 41 |
| Program Configuration Flags | 9 |
| Program List Flags | 7 |
| Registration Flags | 19 |
| Speaker Search Flags | 17 |
| Survey Flags | 2 |
| Alert Flags | 1 |
| Document Template Flags | 32 (8 templates x 4 sub-flags) |
| Company Level Flags | 11 |
| Company Navigation Flags | 8 |
| Database Config Flags | 4 |
| ACTIVE Total | ~165 |
| REMOVED Flags | 18 |
3.3 Business Logic Issues
BL-1: 三层配置来源冲突
- YAML 配置 (
AgencyConfiguration) - 启动时加载,运行时不可变 - 数据库
t_product的product_configJSONB 字段 - 运行时可修改 - 数据库
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 公司级配置边界模糊
speakerLevels和specialities同时出现在CompanyConfiguration和ProductConfiguration- 不清楚哪一层的配置优先
4. API Inventory
4.1 REST Endpoints Table
4.1.1 Configuration API
| Method | Endpoint | 认证 | Controller | 描述 |
|---|---|---|---|---|
| GET | /v1/configuration/instance | Yes | InstanceConfigurationController | 获取实例配置(Agency 级) |
| GET | /v1/configuration/products | Yes | ProductConfigurationController | 获取所有产品配置 |
| GET | /v1/configuration/products/{productId} | Yes | ProductConfigurationController | 获取单个产品配置 |
| GET | /v1/configuration/products/{productId}/speaker-search | Yes | ProductConfigurationController | 获取讲者搜索配置 |
| GET | /v1/configuration/products/{productId}/program | Yes | ProductConfigurationController | 获取项目配置 |
| GET | /v1/configuration/products/{productId}/custom-label | Yes | ProductConfigurationController | 获取自定义标签配置 |
| GET | /v1/configuration/products/{productId}/links | Yes | ProductConfigurationController | 获取链接配置 |
| GET | /v1/public/configuration | No | PublicConfigurationController | 获取完整公共配置(SalesView 用) |
| GET | /v1/public/configuration/product | No | PublicConfigurationController | 获取产品公共配置(通过 Header) |
| GET | /v1/public/configuration/company | No | PublicConfigurationController | 获取公司配置(SpeakerView 用) |
4.1.2 Product API
| Method | Endpoint | 认证 | Controller | 描述 |
|---|---|---|---|---|
| GET | /v1/products/registrationstatus | Yes | ProductController | 获取所有产品注册状态 |
| PUT | /v1/products/registrationstatus | Yes | ProductController | 更新产品注册状态 |
| GET | /v1/products/{productId}/logos | Yes | ProductController | 获取产品 Logo |
| PUT | /v1/products/{productId}/logos | Yes | ProductController | 保存 Logo |
| PUT | /v1/products/{productId}/logos/{programTypeId}/enable | Yes | ProductController | 启用项目类型 Logo |
| PUT | /v1/products/{productId}/logos/{programTypeId}/disable | Yes | ProductController | 禁用项目类型 Logo |
| GET | /v1/products/{productId}/presentations | Yes | ProductController | 获取演示文稿列表 |
| GET | /v1/products/{productId}/otherfiles | Yes | ProductController | 获取其他文件列表 |
| GET | /v1/products/{productId}/default-registration-status | Yes | ProductController | 获取默认注册状态 |
4.1.3 Dictionary API
| Method | Endpoint | 认证 | Controller | 描述 |
|---|---|---|---|---|
| GET | /v1/dictionary/products | Yes | DictionaryController | 产品字典 |
| GET | /v1/dictionary/companies | Yes | DictionaryController | 公司字典 |
| GET | /v1/dictionary/programtypes | Yes | DictionaryController | 项目类型字典(含服务选项) |
| GET | /v1/dictionary/servicetypes | Yes | DictionaryController | 服务类型字典 |
| GET | /v1/dictionary/brands | Yes | DictionaryController | 品牌字典 |
| GET | /v1/dictionary/teams | Yes | DictionaryController | 团队字典 |
| GET | /v1/dictionary/salesforces | Yes | DictionaryController | 销售团队字典 |
| GET | /v1/dictionary/regions | Yes | DictionaryController | 区域字典 |
| GET | /v1/dictionary/districts | Yes | DictionaryController | 地区字典 |
| GET | /v1/dictionary/territories | Yes | DictionaryController | 辖区字典 |
| GET | /v1/dictionary/speakers | Yes | DictionaryController | 讲者字典 |
| GET | /v1/dictionary/sales | Yes | DictionaryController | 销售代表字典 |
| GET | /v1/dictionary/district-managers | Yes | DictionaryController | DM 字典 |
| GET | /v1/dictionary/regional-managers | Yes | DictionaryController | RM 字典 |
| GET | /v1/dictionary/planners | Yes | DictionaryController | Planner 字典 |
| GET | /v1/dictionary/topics | Yes | DictionaryController | 主题字典 |
| GET | /v1/dictionary/venues | Yes | DictionaryController | 场地字典 |
| GET | /v1/dictionary/aggregate-spend-report-fields | Yes | DictionaryController | 聚合支出报告字段 |
| GET | /v1/dictionary/plids | Yes | DictionaryController | PLID 字典 |
4.1.4 CRUD Management API
| Method | Endpoint | 认证 | Controller | 描述 |
|---|---|---|---|---|
| GET | /v1/brands | Yes | BrandController | 品牌列表(分页) |
| POST | /v1/brands | Yes | BrandController | 创建品牌 |
| PUT | /v1/brands/{id} | Yes | BrandController | 更新品牌 |
| DELETE | /v1/brands/{id} | Yes | BrandController | 删除品牌 |
| PUT | /v1/brands/{id}/status | Yes | BrandController | 更新品牌状态 |
| GET | /v1/programtypes | Yes | ProgramTypeController | 项目类型列表 |
| POST | /v1/programtypes | Yes | ProgramTypeController | 创建项目类型 |
| PUT | /v1/programtypes | Yes | ProgramTypeController | 更新项目类型 |
| PUT | /v1/programtypes/{id}/status | Yes | ProgramTypeController | 更新项目类型状态 |
| PUT | /v1/programtypes/planners | Yes | ProgramTypeController | 分配 Planner |
| PUT | /v1/programtypes/{programTypeId}/approvals | Yes | ProgramTypeController | 保存审批配置 |
| GET | /v1/servicetypes | Yes | ServiceTypeController | 服务类型列表 |
| PUT | /v1/servicetypes | Yes | ServiceTypeController | 保存服务类型 |
| PUT | /v1/servicetypes/{serviceTypeId}/virtual-program-info | Yes | ServiceTypeController | 更新虚拟项目信息 |
| GET | /v1/teams | Yes | TeamController | 团队列表 |
| POST | /v1/teams | Yes | TeamController | 创建团队 |
| PUT | /v1/teams/{id} | Yes | TeamController | 更新团队 |
| DELETE | /v1/teams/{id} | Yes | TeamController | 删除团队 |
| PUT | /v1/teams/{id}/status | Yes | TeamController | 更新团队状态 |
| GET | /v1/attendance/{programTypeId} | Yes | AttendanceController | 获取出席配置 |
| POST | /v1/attendance | Yes | AttendanceController | 保存出席配置 |
| GET | /v1/preferred-speaker/{productId} | Yes | PreferredSpeakerController | 获取首选讲者配置 |
| POST | /v1/preferred-speaker | Yes | PreferredSpeakerController | 保存首选讲者配置 |
| GET | /v1/force-target-invitation/config/{teamId} | Yes | ForceTargetInvitationController | 获取强制目标邀请配置 |
| POST | /v1/force-target-invitation/config | Yes | ForceTargetInvitationController | 保存强制目标邀请配置 |
| GET | /v1/force-target-invitation/targets | Yes | ForceTargetInvitationController | 获取强制邀请目标列表 |
4.2 API Design Issues
API-1: RESTful 命名不规范
GET /v1/products/registrationstatus- 缺少连字符,应为registration-statusGET /v1/dictionary/programtypes- 应为program-typesGET /v1/dictionary/servicetypes- 应为service-typesGET /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-IDHeader 获取产品 ID- 文件:
modules/v1/config/controller/PublicConfigurationController.java:47 - 不符合 REST 惯例,应通过路径参数传递
API-5: 字典 API 与 CRUD API 重复
GET /v1/dictionary/brands和GET /v1/brands返回类似数据GET /v1/dictionary/teams和GET /v1/teams返回类似数据GET /v1/dictionary/programtypes和GET /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 Users | User | components/User/ | PROGRAM_ADMIN |
| Push Alerts | PushAlert | components/PushAlert/ | PROGRAM_ADMIN |
| Budget Categories | BudgetCategories | components/BudgetCategories/ | PROGRAM_ADMIN |
| Program Types | ProgramType | components/ProgramType/ | PROGRAM_ADMIN |
| Budget Templates | BudgetTemplate | components/BudgetTemplate/ | PROGRAM_ADMIN |
| Brand | Brand | components/Brand/ | PROGRAM_ADMIN |
| Setup Sales Force | SalesForce | components/SalesForce/ | PROGRAM_ADMIN |
| Setup Team | Team | components/Team/ | PROGRAM_ADMIN |
| Attendance Frequency | AttendanceFrequency | components/AttendanceFrequency/ | PROGRAM_ADMIN |
| Registration Workflow | RegistrationWorkflow | components/RegistrationWorkflow/ | PROGRAM_ADMIN |
| Force Target Invitation | ForceTargetInvitation | components/ForceTargetInvitation/ | PROGRAM_ADMIN + enableForceTargetInvitation flag |
| Email Invitation Templates | EmailInvitationTemplates | components/EmailInvitationTemplates/ | PROGRAM_ADMIN |
| Email Sandbox | EmailSandbox | components/EmailSandbox/ | PROGRAM_ADMIN |
| Document Templates | Template | components/Template/ | PROGRAM_ADMIN + DOCUMENT_TEMPLATE_ADMIN |
| Compliance Document Checklist | DocumentChecklist | containers/Compliance/DocumentChecklist/ | PROGRAM_ADMIN + AUDIT |
| Preferred Speakers List | PreferredSpeakersList | components/PreferredSpeakersList/ | PROGRAM_ADMIN + enablePreferredSpeakersList flag |
| Configure Program Task Templates | ProjectTask | components/ProjectTask/ | PROGRAM_ADMIN + projectTaskEnabled flag |
| Configure Aggregate Spend Reports | AggregateSpendReportConfiguration | components/AggregateSpendReportConfiguration/ | PROGRAM_ADMIN |
| Logos | UploadLogos | components/UploadLogos/ | 无条件 |
| Surveys | Surveys | components/Surveys/ | SURVEY_ADMIN |
| Unsubscribed List | UnsubscribedList | components/UnsubscribedList/ | enableUnsubscribedEmail flag |
| SFTP | SFTP | components/SFTP/ | 无条件 |
Advanced 分组:
| 菜单项 | 组件 | 文件路径 | 条件 |
|---|---|---|---|
| Multi-Module Users | PharmaginUser | components/PharmaginUser/ | MULTI_MODULE_USER_ADMIN |
| User Roles | PharmaginRole | components/PharmaginRole/ | MULTI_MODULE_USER_ADMIN |
| Users Limit | UsersLimit | components/UsersLimit/ | MULTI_MODULE_USER_ADMIN |
| Download Change Log | DownloadChangeLog | containers/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
// 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()- 获取完整 instanceConfigselectProductConfig(productId)- 获取特定产品配置
SalesView
// State shape: app.globalConfig (ProductConfiguration)
{
salesViewUrl: String,
reconciliationType: Number,
switchProductEnabled: Boolean,
program: { ... },
registration: { ... },
speakerSearch: { ... },
// ... all ProductConfiguration fields
}SpeakerView
// 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-3 | 165+ Feature Flags 全部嵌入 YAML | 运维 | 所有 feature flag 需要修改 YAML 并重启服务才能生效,无法运行时动态切换。任何客户的功能启停都需要重新部署 |
| C-4 | ProductConfiguration 同名类冲突 | 代码可维护性 | common.persistence.entity.ProductConfiguration (DB entity, 3 fields) 与 modules.v1.config.ProductConfiguration (YAML POJO, 100+ fields) 完全不同但同名 |
| C-5 | FiscalPeriod 硬编码到 2025 | 功能缺陷 | FiscalPeriod.java:11-22 硬编码年份列表到 2025,2026 年开始功能失效 |
6.2 Design Defects (should improve)
| ID | 问题 | 影响范围 | 详情 |
|---|---|---|---|
| D-1 | Feature Flag 命名不一致 | 开发体验 | enable* 和 disable* 混用,如 enableScheduledReports vs disableSurveyAlerts,易导致前端逻辑错误 |
| D-2 | 配置层级重叠 | 业务逻辑 | speakerLevels 和 specialities 同时出现在 CompanyConfiguration 和 ProductConfiguration,优先级不明 |
| D-3 | 字典 API 与 CRUD API 重复 | API 设计 | GET /v1/dictionary/brands 和 GET /v1/brands 返回类似数据,增加维护成本 |
| D-4 | JSONB 字段无 Schema 验证 | 数据完整性 | Product.productConfig, ProductConfiguration.logos, ProgramServiceType.spaceConfig 等 JSONB 字段类型为 Object,无结构约束 |
| D-5 | Region/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-8 | DynamicProgramField 过度灵活 | 前端复杂度 | required 和 visible 字段类型为 Object,可以是布尔值也可以是表达式字符串(如 "{ data.fieldExhibit === true ? { display: 'none' } : '' }"),前端需要 eval 处理 |
6.3 Technical Debt (nice to have)
| ID | 问题 | 影响范围 | 详情 |
|---|---|---|---|
| T-1 | Topic 实体未使用 Lombok | 代码风格 | Topic.java 手写 getter/setter 140+ 行,与其他实体不一致 |
| T-2 | Team 冗余外键 | 数据模型 | Team 同时有 salesTeamId 和 salesForceId,语义重叠 |
| T-3 | 前端无 TypeScript 类型 | 开发效率 | 100+ feature flags 无编译时类型检查 |
| T-4 | 配置双重存储 | 前端架构 | PlannerView/SpeakerView 在 Redux 和 sessionStorage 中双重存储配置 |
| T-5 | REST URL 命名不规范 | API 规范 | registrationstatus, programtypes, servicetypes 缺少连字符 |
| T-6 | PUT 用于创建 | REST 规范 | PUT /v1/programtypes 同时处理创建和更新 |
| T-7 | 无配置变更审计 | 合规 | YAML 配置变更无审计日志(数据库变更通过 JaVers 有审计) |
| T-8 | VirtualProgramConfig 包含 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)
- 创建专用的
PublicProductConfigDTO,而不是直接返回AgencyConfiguration
R-5: 合并字典和 CRUD API
- 使用查询参数区分完整数据和精简数据:
GET /v1/brands?view=dictionaryvsGET /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 中加入审计日志
- 记录谁在什么时候修改了什么配置