- 后端
- 前端
【免费下载链接】flagsmith
Flagsmith is an open-source feature flag platform with remote config, experimentation, and self-hosted or cloud deployment options.
本文以 Flagsmith 官方文档《Import from LaunchDarkly》为核心,系统讲解如何将 LaunchDarkly 项目中的功能开关(Flags)与用户分群(Segments)迁移到 Flagsmith:从 Access Token 生成、项目导入配置,到布尔/多值标志、归档标志、分群规则的映射细节,并深入api/integrations/launch_darkly/下的源码实现,说明底层 API 调用、事务处理、运算符换算与速率限制机制,帮助你安全、可预期地完成迁移并在导入后自行核验结果。
迁移前须知:两种产品的数据模型差异
Flagsmith 与 LaunchDarkly 在产品设计和底层数据模型上存在差异。官方文档特别提示:导入器(importer)会基于「合理决策」对数据做映射迁移,但强烈建议在导入完成后人工检查导入结果(见 import-from-launchdarkly.md)。
另一个必须提前知晓的要点:导入操作会覆盖项目中已有的环境和标志(Import operations will overwrite existing environments and flags in your project)。因此官方推荐(也是更稳妥的做法)是新建一个 Flagsmith 项目再执行导入,避免与现有环境、标志冲突。
前置条件:生成 LaunchDarkly Access Token
迁移只需要一个凭据:LaunchDarkly Access Token。生成步骤如下:
- 登录你的 LaunchDarkly 账号;
- 进入Account settings(账号设置)>Authorization(授权)>Access tokens(访问令牌);
- 创建一个新的 Access Token(建议使用只读或受限权限的 Token,因为导入过程只读取 LaunchDarkly 侧数据)。
该 Token 会被粘贴到 Flagsmith 侧发起导入,整个过程由 Flagsmith 作为客户端调用 LaunchDarkly API 拉取数据。
集成配置:在 Flagsmith 中发起导入
完成 Token 生成后,按以下步骤在 Flagsmith 中建立导入集成:
- 导入到 Flagsmith:
- 在 Flagsmith 中新建一个项目,或选择已有项目(如前所述,推荐新建);
- 进入project settings(项目设置)>Import(导入);
- 将上一步生成的 Access Token 粘贴到提供的输入框中。
- 开始导入:添加 Token 后导入会立即开始,无需额外点击「确认」或「提交」步骤。
从源码看,前端填写的是token与project_key两个字段,其中project_key是 LaunchDarkly 侧的项目标识。后端视图(views.py)在创建导入请求后会立即调用:
process_launch_darkly_import_request.delay( kwargs={"import_request_id": instance.id} )即导入请求创建成功后,通过任务队列异步触发后台执行,因此你在界面上看到「导入已开始」后,实际数据同步在后台进行。
导入流程与执行机制(源码视角)
为了让你理解「粘贴 Token 后发生了什么」,这里梳理api/integrations/launch_darkly/下的完整调用链:
- 创建导入请求(services.py 中
create_import_request):- 用
LaunchDarklyClient(ld_token)建立 API 客户端; - 调用
get_project(project_key)获取 LaunchDarkly 项目及环境总数; - 调用
get_flag_count(project_key)获取标志总数; - 将环境数、标志数、错误消息列表写入
statusJSON 字段,生成LaunchDarklyImportRequest记录; - Token 不会明文存储:它通过 Django
signing.dumps以ld_import_{user_id}为盐进行签名加密后落库。
- 用
- 异步执行(tasks.py):任务处理器
process_launch_darkly_import_request取出请求记录,调用process_import_request执行真实导入;若遭遇速率限制(LaunchDarklyRateLimitError)则抛出TaskBackoffError(delay_until=retry_at)让任务延迟重试;其他异常只记录日志不再重试(因为失败后 Token 凭据已被清除,重试没有意义)。 - 数据拉取(client.py):
- 获取项目下全部环境(
/api/v2/projects/{key}/environments); - 按环境批量拉取标志:
/api/v2/flags/{project_key},每个请求最多传 3 个环境(LAUNCH_DARKLY_API_MAX_ENVIRONMENTS_PER_REQUEST = 3),每页最多 100 个标志(LAUNCH_DARKLY_API_FLAGS_LIMIT_PER_PAGE = 100); - 拉取标志标签(
/api/v2/tags?kind=flag); - 按「环境 × 环境」拉取用户分群(
/api/v2/segments/{project_key}/{environment_key},每页 50 条,使用 offset 分页)。
- 获取项目下全部环境(
- 写入 Flagsmith(
process_import_request):在单个数据库事务(transaction.atomic())内依次完成「创建环境 → 创建分群 → 创建标志」,最后统计弃用标志数量并在提交后刷新分群成员数(enqueue_membership_refresh)。 - 状态追踪与幂等保护:
LaunchDarklyImportRequest.status记录requested_environment_count、requested_flag_count、deprecated_flag_count、result(success/failure/incomplete)和error_messages;- 模型(models.py)上有唯一约束:同一项目 + 同一
ld_project_key只允许一个「进行中(status.result 为空)」的导入请求,重复提交会被拦截并返回「Existing import already in progress for this project」。
导入内容详解
官方文档规定,每个被导入的 LaunchDarkly 项目,都会在 Flagsmith 中创建一个新项目,并复制以下实体。
Environments(环境)
LaunchDarkly 项目内的所有环境都会按name复制到 Flagsmith 中成为对应环境(Environment.objects.get_or_create(name=..., project_id=...),同名环境复用,不重复创建)。
Flags(标志)
LaunchDarkly 的标志按类型分别处理:
Boolean flags(布尔标志)
- 导入为 Flagsmith 中开启/关闭状态正确、但不设置标志值的普通标志;
- 布尔状态取自 LaunchDarkly 标志配置中的
_summary -> on字段; - 对应源码
_create_boolean_feature_states_with_segments_identities:FeatureState.objects.update_or_create(..., defaults={"enabled": ld_flag_config["on"]}),且创建空的FeatureStateValue(即无值)。
Multivariate flags(多值标志)
- 导入为 Flagsmith 的Multivariate(多值)标志值;
- 多值取自 LaunchDarkly 的
variations字段; - 「关闭时返回的值」(targeting off 时服务的值)会被导入为控制值(control value)。源码中该逻辑在
_create_string_feature_states_with_segments_identities(2 个 variation)和_create_mv_feature_states_with_segments_identities(超过 2 个 variation)中体现:从_summary的 variations 配置里找isOff标记的 variation 作为控制值,isFallthrough标记的 variation 分配 100% 权重。
Archived and deprecated flags(归档与弃用标志)
- LaunchDarkly 中已归档(archived)或已弃用(deprecated)的标志,导入为 Flagsmith 中已归档(archived)的标志;
- 源码:
Feature.objects.update_or_create(..., defaults={"is_archived": ld_flag["archived"] or ld_flag["deprecated"]})。
Segments(用户分群)
官方文档在开篇即说明本指南覆盖「flags and segments」的迁移。从源码看,分群导入(_create_segments_from_ld)的实现如下:
- LaunchDarkly 的分群以「分群名 (Override for {环境名})」命名导入为 Flagsmith 分群,名称中带有环境信息以保证跨环境唯一(
_get_segment_name),该命名在重复导入时复用,避免重复创建; - 分群规则中的每个 clause 都会映射为 Flagsmith 的 SegmentRule 结构:顶层为 ALL(AND)规则,非否定条件挂到 ANY(OR)子规则,否定条件统一收集到一个 NONE 子规则中(因为 Flagsmith 没有直接的「非 X」运算符);
- 分群的
included/excluded用户列表会转成基于key属性、运算符为in的条件子规则,并同步为对应环境的 Identity 记录及其keytrait; - 已删除(
deleted)的 LaunchDarkly 分群会被跳过。
Tags(标签)
导入会为标志打上 LaunchDarkly 原始标签,并额外附加一个默认标签Imported(色值#3d4db6),便于你在迁移后快速筛选出所有导入产生的标志(见 constants.py)。分群的标签暂不支持(源码中留有 TODO 注释)。
底层映射规则:LaunchDarkly 运算符 → Flagsmith 运算符
为了让迁移后的分群规则仍然「按语义工作」,导入器实现了运算符映射表(_ld_operator_to_flagsmith_operator,services.py):
| LaunchDarkly 运算符 | Flagsmith 运算符 | 说明 |
|---|---|---|
in | IN | 多个值以逗号拼接为单条件 |
endsWith | REGEX | 转为.*值$正则 |
startsWith | REGEX | 转为^值正则 |
matches | REGEX | 直接使用 |
contains | CONTAINS | 直接使用 |
lessThan | LESS_THAN | 直接使用 |
lessThanOrEqual | LESS_THAN_INCLUSIVE | 直接使用 |
greaterThan | GREATER_THAN | 直接使用 |
greaterThanOrEqual | GREATER_THAN_INCLUSIVE | 直接使用 |
before | LESS_THAN | 时间比较映射 |
after | GREATER_THAN | 时间比较映射 |
semVerEqual | EQUAL | 值追加:semver后缀 |
semVerLessThan | LESS_THAN | 值追加:semver后缀 |
semVerGreaterThan | GREATER_THAN | 值追加:semver后缀 |
配套的值转换规则(_convert_ld_values):
in:多个值合并为逗号分隔字符串,并按SEGMENT_CONDITION_VALUE_LIMIT上限分块;startsWith:每个值转成^转义值;endsWith:每个值转成.*转义值$;semVer*:每个值追加:semver;- 其余运算符原样透传。
此外,多值标志的百分比分配也会做换算:LaunchDarkly 的权重范围为0–100,000(千分比),Flagsmith 为0–100,源码通过维护cumulative_rollout累计值并四舍五入,保证换算后各 variation 权重之和仍为 100%。标志类型判定逻辑为:variations > 2判定为 Multivariate 标志;恰好 2 个 variation 时作为带开关值的标准字符串标志导入;否则按布尔标志处理。
已知限制与不支持场景
以下 LaunchDarkly 能力在当前导入器(基于 API version20240415)中不被支持,相关内容会被跳过,并在导入请求的status.error_messages中记录具体原因(导入完成后可在导入结果中查看):
- Context targets(上下文定向):
contextTargets被跳过并记录错误; - Prerequisites(前置标志依赖):被跳过并记录错误;
- 嵌套的 segmentMatch(嵌套分群匹配):
segmentMatch子句与其他运算符混用时无法映射为 Flagsmith FeatureSegment,被跳过; - 否定式分群匹配(negated segment match):被跳过;
- 无法映射的 LaunchDarkly 运算符(映射表之外的运算符,如
not in等):被跳过并记录错误; - 超过
SEGMENT_CONDITION_VALUE_LIMIT长度上限的条件值 / 定向标识:被跳过; - 分群的 included/excluded Contexts(上下文列表):不支持,被跳过并记录错误。
可以推断:这类「尽力而为 + 显式报错」的设计,正是官方文档要求「导入完成后人工检查结果」的根本原因——被跳过的规则不会悄悄丢失,而是会出现在错误清单中供你手动补充。
速率限制与重试机制
导入器按 LaunchDarkly 官方 API 文档实现了完整的速率限制处理(client.py 的launch_darkly_backoff):
- 请求返回429 Too Many Requests时自动退避重试;
- 退避时长依次取
Retry-After响应头、X-Ratelimit-Reset响应头(毫秒级时间戳,若已过期则用默认值)、否则默认10 秒; - 最多重试5 次(
BACKOFF_MAX_RETRIES),并附加随机抖动(backoff.random_jitter); - 5 次仍被限流时抛出
LaunchDarklyRateLimitError,由任务层转换为TaskBackoffError延迟到限流窗口结束再重跑整次导入; - 若最终 429 之外的其他错误,导入请求标记为
failure,同时清空已存储的加密 Token,保证凭据不残留。
导入完成后的核验建议
官方文档明确指出「强烈建议导入完成后人工检查结果」。落地建议包括:
- 进入导入请求详情,查看
requested_environment_count、requested_flag_count、deprecated_flag_count与实际导入数量是否吻合; - 逐条查看
error_messages中列出的跳过项(不支持运算符、context targets、前置条件等),在 Flagsmith 中手动补齐; - 抽样验证布尔标志的
_summary.on状态、多值标志的控制值、分群规则的成员覆盖是否与 LaunchDarkly 一致; - 核对
Imported标签下的标志是否完整,确认归档标志是否正确归档。
如需深入了解实现细节,可继续阅读本仓库源码:
- 导入核心逻辑:api/integrations/launch_darkly/services.py
- LaunchDarkly API 客户端与限流退避:api/integrations/launch_darkly/client.py
- 导入请求模型与状态字段:api/integrations/launch_darkly/models.py
- 异步任务与重试调度:api/integrations/launch_darkly/tasks.py
- API 常量(分页上限、标签、限流参数):api/integrations/launch_darkly/constants.py
- 单元测试:api/tests/unit/integrations/launch_darkly/test_services.py、api/tests/unit/integrations/launch_darkly/test_client.py、api/tests/unit/integrations/launch_darkly/test_views.py
- 后端
- 前端
【免费下载链接】flagsmith
Flagsmith is an open-source feature flag platform with remote config, experimentation, and self-hosted or cloud deployment options.
相关推荐
SilentPatch 安装指南:一次修复 GTA III、VC 与圣安地列斯的现代系统兼容性顽疾
SilentPatch 安装指南:一次修复 GTA III、VC 与圣安地列斯的现代系统兼容性顽疾 深夜十一点,你双击圣安地列斯的图标,屏幕黑了一下,弹出半句英
游戏开发逆向工程从 Highlight.io 迁移到 LaunchDarkly Observability:SDK 迁移指南与配置对照
从 Highlight.io 迁移到 LaunchDarkly Observability:SDK 迁移指南与配置对照 本篇指南聚焦 Highlight.io
可观测性后端从 Obsidian 迁移到 Notesnook:Vault 导入的完整实战指南与源码原理解析
从 Obsidian 迁移到 Notesnook:Vault 导入的完整实战指南与源码原理解析 Obsidian 用户想要迁移到完全开源、端到端加密的笔记应用
前端移动开发桌面应用应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考