/** * 业务数量参数(规范 §118 App 配置 + AGENTS.md 第 12 条"所有未确认数量参数配置化") * * 全部数量限制走配置,禁止页面层硬编码业务常量。 * 实际运行时由 services/appConfigService 从服务端拉取并覆盖默认值(规范 §118)。 * 配置接口失败时使用本文件的安全默认值(规范 §119)。 */ exports.BUSINESS_LIMITS = { // 推荐相关 DAILY_RECOMMENDATION_LIMIT: 6, // 每日推荐人数 RECOMMENDATION_PAGE_SIZE: 10, // 推荐分页大小 // 资料相关 MAX_PROFILE_PHOTOS: 6, // 生活照上限 MAX_INTEREST_TAGS: 8, // 兴趣标签上限(规范 §35 不超过 8 个) MIN_INTRO_LENGTH: 20, // 自我介绍最小字数 MAX_INTRO_LENGTH: 500, // 自我介绍最大字数 MAX_REPORT_LENGTH: 500, // 举报说明最大字数 MAX_FEEDBACK_LENGTH: 500, // 意见反馈最大字数 // 聊天相关 CHAT_IMAGE_ENABLED: false, // V1 默认关闭图片聊天(规范 §76 Feature Flag) CHAT_MESSAGE_MAX_LENGTH: 1000, CHAT_HISTORY_PAGE_SIZE: 30, // 聊天历史分页 // 功能开关(规范 §76) ACTIVITY_ENABLED: true, MATCHMAKER_ENABLED: true, ENABLE_RISK_KEYWORD_WARNING: true, // 后台分页(规范 §82) PAGE_SIZE_DEFAULT: 20, PAGE_SIZE_OPTIONS: [20, 50], // 时长(ms) TOAST_DURATION: 1500, // 规范 §56:1.5~2 秒 BUTTON_PRESS_FEEDBACK: 120, // 按钮按压反馈时长 // 网络请求 REQUEST_TIMEOUT_MS: 15000, REQUEST_RETRY_COUNT: 1, // 联系方式 SUPPORT_CONTACT: '', }; /** * 服务端 /app/config 的嵌套结构 → 客户端 UPPER_SNAKE 常量的显式映射。 * *
此前用平铺合并 {@code {...BUSINESS_LIMITS, ...overrides}},命不中嵌套 key * (服务端返回的是 {@code businessLimits.maxProfilePhotos} / {@code features.x}), * 导致服务端下发的照片上限 / 推荐阈值等配置从不生效 —— 前后端规则漂移 * (V1.0.1 配置纠偏 PROFILE-001 前端侧根因)。现改为按节(section)+ 键显式映射, * 且仅在「服务端给了该键 && 客户端确有该常量」时才覆盖,缺省回退本地默认值。 */ const SERVER_SECTION_TO_CLIENT_KEY = { businessLimits: { dailyRecommendationLimit: 'DAILY_RECOMMENDATION_LIMIT', maxProfilePhotos: 'MAX_PROFILE_PHOTOS', maxInterestTags: 'MAX_INTEREST_TAGS', selfIntroMaxLength: 'MAX_INTRO_LENGTH', }, features: { activityEnabled: 'ACTIVITY_ENABLED', matchmakerEnabled: 'MATCHMAKER_ENABLED', imageChatEnabled: 'CHAT_IMAGE_ENABLED', }, }; /** * 用服务端返回的配置覆盖默认值(规范 §118) * @param {object} serverConfig 服务端 /app/config 的完整响应(含 businessLimits / features 等嵌套节) * @returns {typeof exports.BUSINESS_LIMITS} 合并后的运行时配置 */ exports.mergeAppConfig = (serverConfig) => { const merged = { ...exports.BUSINESS_LIMITS }; if (!serverConfig || typeof serverConfig !== 'object') return merged; for (const [section, keyMap] of Object.entries(SERVER_SECTION_TO_CLIENT_KEY)) { const src = serverConfig[section]; if (!src || typeof src !== 'object') continue; for (const [serverKey, clientKey] of Object.entries(keyMap)) { if (Object.prototype.hasOwnProperty.call(src, serverKey) && Object.prototype.hasOwnProperty.call(merged, clientKey)) { merged[clientKey] = src[serverKey]; } } } return merged; };