ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于 BISHENG F015 的 LDAP/SSO 部门定时校对与 relink 换型:14 条 AC 的自动化与手工验证体系

基于 BISHENG F015 的 LDAP/SSO 部门定时校对与 relink 换型:14 条 AC 的自动化与手工验证体系 基于 BISHENG F015 的 LDAP/SSO 部门定时校对与 relink 换型14 条 AC 的自动化与手工验证体系【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng本文以 BISHENG 开源仓库features/v2.5.1/015-ldap-reconcile-celery/ac-verification.md为核心骨架系统讲解「部门定时校对每 6 小时 Celery 全量 reconcile SSO 换型 relink」这一多租户组织同步功能的完整验证矩阵从 14 条验收标准AC如何被 60 条自动化测试与手工 QA 脚本覆盖到OrgReconcileService/RemoteDeptDiffer/OrgSyncTsGuard等源码级实现如何支撑这些断言再到发版前 10 万部门压测专项与 E2E 执行记录。读者可据此理解该功能的验收口径、复现验证环境并掌握把时间窗口类/UI 交互类需求落成可执行验证矩阵的方法。一、功能背景F015 解决什么问题在 BISHENG 的多租户体系v2.5.1中组织数据来自 LDAP/OAuth/Feishu/DingTalk/WeCom 等 SSO 源。F015部门定时校对 SSO 换型 relinkP1 优先级要回答集团 IT 的两个诉求系统每 6 小时自动校对 SSO 端部门树与 bisheng 内部结构发现漂移新增/改名/删除/跨租户移动时自动修复SSO 换 HR 系统时提供 relink 接口重建 external_id 映射换型不丢失用户归属。完整规格见 spec.md任务拆解见 tasks.md。核心能力可概括为五点Celery Beat 定时任务每 6h 全量校对 SSO 部门树 vs bisheng漂移处理策略新增/命名变更自动同步删除进入 orphanedis_deleted1移动触发UserTenantSyncServiceGateway 实时 vs Celery 校对冲突处理INV-T12每 external_id 维护last_sync_ts按 ts 最大为准同 ts 下 upsertremove 冲突以 remove 为准新增POST /api/v1/internal/departments/relinkSSO 换型时 pathname 回落匹配冲突列表需管理员逐项确认周报聚合告警。二、14 条 AC 验收标准总览ID角色操作预期结果AC-01系统Celery Beat 定时触发每 6h 执行一次 SSO 全量校对AC-02系统发现 SSO 新部门自动 upsertmount 状态保留不动标记AC-03系统发现 SSO 删除部门bisheng 标记is_deleted挂载点进入 orphanedAC-04系统发现主部门跨 Tenant 变更触发UserTenantSyncServiceAC-05运维POST relink策略external_id_map显式映射应用成功AC-06运维POST relink策略path_plus_name单候选自动 apply多候选返回 conflicts 列表AC-07运维relinkdry_runtrue返回 would_apply 清单但不写入AC-08性能10 万部门全量校对执行时间 30 minMySQL/Redis 压力可控AC-09系统Gateway 实时 tsT1 后 Celery 推 tsT0 (T0T1)跳过该消息 写org_sync_loglevelwarn不覆盖当前状态AC-10系统Celery 推 tsT2 (T2T1)应用变更更新last_sync_tsT2AC-11系统同 ts 下 upsert 与 remove 冲突以 remove 为准写audit_log 站内消息调用DepartmentDeletionHandler.on_deleted(dept_id, celery_reconcile)AC-12系统单 external_id 一周 ≥3 次冲突每周一 09:00 聚合周报连续 5 天未解决升级每日告警AC-13系统并发同 external_id 同 ts 同方向消息Redis SETNX 去重幂等返回架构决策AD同步记录在 spec.md校对频率取 6hAD-01、relink 冲突人工确认AD-02、实时 vs 校对以最新 ts 优先AD-03、同 ts 冲突从严以 remove 为准AD-04。三、自动化验证矩阵60 条测试如何覆盖 131 条 ACac-verification.md第 1 节给出了完整的自动化覆盖矩阵这是全篇的验证契约AC关键任务测试文件关键用例AC-01T04, T09test_reconcile_celery_tasks.pyTestBeatSchedule::test_beat_schedule_registers_reconcile_all_every_6h/TestFanOut::test_fan_out_dispatches_single_config_per_active_configAC-02T05, T06test_remote_dept_differ.py、test_org_reconcile_service.pyTestUpserts::test_diff_rename_generates_upsert_op_preserves_existing_id/TestReconcileHappyBranches::test_reconcile_new_dept_upserts_preserves_mountAC-03T06test_org_reconcile_service.pytest_reconcile_removed_dept_triggers_department_deletion_handler断言deletion_sourceCELERY_RECONCILEAC-04T05, T06test_remote_dept_differ.py、test_org_reconcile_service.pyTestMoveCrossTenant::test_diff_move_across_tenant_marks_crosses_tenant_true/test_reconcile_primary_dept_change_triggers_user_tenant_sync断言triggerCELERY_RECONCILEAC-05T07, T08test_department_relink_service.py、test_relink_api_integration.pyTestExternalIdMapStrategy::test_external_id_map_strategy_applies/TestRelinkRoute::test_relink_route_responds_200_with_valid_hmacAC-06T07, T08, T12test_department_relink_service.py、test_relink_conflict_store.pyTestPathPlusNameStrategy::test_path_plus_name_single_candidate_auto_apply/test_path_plus_name_multi_candidate_returns_conflicts/TestResolveConflict::test_resolve_conflict_applies_chosen_candidate/TestRelinkConflictStore::test_save_and_get_returns_candidates_listAC-07T07, T08test_department_relink_service.py、test_relink_api_integration.pyTestDryRun::test_dry_run_returns_would_apply_no_db_write/test_relink_dry_run_via_http_returns_would_applyAC-08T15scripts/performance/locust_ldap_reconcile_100k.py占位发版前专项不在 CIAC-09T06, T11test_org_reconcile_service.pyTestTsGuardBranches::test_reconcile_stale_ts_skipped_writes_warn_event/TestEventRowPersistence::test_stale_ts_event_written_with_level_warn_and_event_type_stale_tsAC-10T06test_org_reconcile_service.pyTestTsGuardBranches::test_reconcile_newer_ts_applies_and_updates_last_sync_tsAC-11T06, T11test_org_reconcile_service.pytest_reconcile_same_ts_upsert_then_remove_prefers_remove断言 auditdept.sync_conflict ts_conflict event 行 19317 warn log/TestEventRowPersistence::test_same_ts_conflict_event_written_with_external_id_and_source_tsAC-12T03, T10, T13test_ts_conflict_reporter.py、test_reconcile_celery_tasks.pyTestWeeklyReport::test_weekly_report_aggregates_conflicts_above_threshold/TestDailyEscalation::test_daily_escalation_triggers_after_5_days_unresolved/test_beat_registers_weekly_conflict_report_monday_09/test_beat_registers_daily_escalation_09AC-13T14test_org_reconcile_service.pyTestLockSemantics四连同 ts 同 config 被 SETNX 去重、不同 config 锁独立、异常时锁释放、TTL 30min 防卡死对应的测试文件已合入仓库主干test/celery/test_reconcile_celery_tasks.py、test/org_sync/test_org_reconcile_service.py、test/org_sync/test_remote_dept_differ.py、test/department/test_department_relink_service.py、test/department/test_relink_api_integration.py、test/department/test_relink_conflict_store.py、test/e2e/test_e2e_ldap_reconcile.py。测试用例统计合计 60 条test_org_sync_log_dao_f015.py9 条T03 T04test_remote_dept_differ.py6 条T05test_org_reconcile_service.py16 条T06 T11 T14test_department_relink_service.py7 条T07test_relink_api_integration.py4 条T08test_reconcile_celery_tasks.py7 条T09 T13test_ts_conflict_reporter.py6 条T10test_relink_conflict_store.py5 条T12四、源码级验证点AC 背后的实现原理验证矩阵中的断言并非黑盒拍脑袋每条 AC 都能在源码中找到对应实现。下面按功能域逐一对应。4.1 AC-01Celery Beat 6h 校对注册reconcile_all_organizations作为独立 Beat 任务不复用 F009 面向用户 cron 的check_org_sync_schedules后者响应用户配置F015 是系统强制通过crontab.from_string(0 */6 * * *)注册到settings.py的CeleryConffan-out 时用OrgSyncConfigDao.aget_all_active()拉取所有租户 active 配置并逐个reconcile_single_config.apply_async(queueknowledge_celery)跳过 F014 的sso_realtimeseedid9999仅供 HMAC 实时日志不喂给 provider。对应测试断言 beat schedule 中的 cron 为crontab(minute0, hour*/6)。4.2 AC-02/03/04Diff 引擎与主编排RemoteDeptDifferremote_dept_differ.py是纯函数 diff内部复用 F009 的reconcile_departments拓扑排序upsert 父先子后、archive 子先父后并为每个 op 打上两个 F015 专属注解incoming_tsCelery beat 启动时捕获的源系统时间戳喂给OrgSyncTsGuardINV-T12MoveOp.crosses_tenant沿 local 祖先链找最近is_tenant_root1节点取其mounted_tenant_id与移动后新父链的 leaf tenant 对比不同即为跨租户。UpsertOp的is_new标记区分 CREATE/UPDATE 语义rename 场景生成 upsert 但不动is_tenant_root挂载标记AC-02 的核心断言。OrgReconcileServicereconcile_service.py是 11 步主编排加载 config跳过sso_realtimeseed / 非 active / 不存在Redis SETNX 锁org_reconcile:{config_id}TTL1800sproviderauthenticate()fetch_departments(sync_scope.root_dept_ids)DepartmentDao.aget_active_by_tenant(tenant_id)取本地快照RemoteDeptDiffer.diff(...)得到ReconcileDiff{upserts, archives, moves}Upsert 循环每 op 过OrgSyncTsGuard.check_and_updateSKIP_TS 记stale_ts事件APPLY 走DeptUpsertService.upsert_from_sync_payloadArchive 循环检测同 ts 的 upsert→remove 碰撞AC-11写ts_conflict事件行 audit_log.actiondept.sync_conflict随后aarchive_by_external_idDepartmentDeletionHandler.on_deleted(dept_id, CELERY_RECONCILE)INV-T8 孤儿 Tenant 统一处理跨租户 moves对部门主成员逐个UserTenantSyncService.sync_user(uid, triggerCELERY_RECONCILE)INV-T2JWTtenant_idtoken_version刷新flush_log(buffer, trigger_typereconcile, config_id)写批摘要行逐条持久化 event 行stale_ts/ts_conflict单行失败不阻断整体返回ReconcileResultapplied_upsert / applied_archive / skipped_ts / conflicts / errors供 Beat 记录。值得注意的细节Celery 入口没有 HTTP 中间件因此reconcile_config在锁内用bypass_tenant_filter() set_current_tenant_id(config.tenant_id or 1)手工安装租户上下文否则 SQLAlchemy 的 tenant_filter 事件会把跨租户维护扫描误判为 20004 Missing tenant context——这正是 E2E 暴露并修复的真实 bug 之一见第八节。4.3 AC-09/10/11OrgSyncTsGuard 决策表ts_guard.py 是 F014Gateway 实时与 F015Celery 校对共享的纯决策函数决策与写库分离便于无 DB 单测。决策规则条件决策incoming_ts last_sync_tsSKIP_TS陈旧消息丢弃写org_sync_loglevelwarn不覆盖当前状态→ AC-09同 ts 且is_deleted1remove 已落地再来 upsertSKIP_TSremove 胜出其他APPLY → AC-10应用并更新last_sync_tsincoming_ts同 ts 下 upsertremove 双向冲突的remove 优先INV-T12从严避免幽灵部门在_apply_archive中实现当existing.last_sync_ts op.incoming_ts且is_deleted0时判定为冲突写入ts_conflictwarn 事件行含 external_id source_ts落audit_log.actiondept.sync_conflictmetadata 含resolutionremove_wins, viacelery_reconcile再打印 19317 warn 日志——错误类SsoSameTsRemoveAppliedWarnError被记录但不抛出管理员通过事件行与 audit 观察而非 HTTP 面。4.4 AC-13Redis SETNX 并发幂等_acquire_lock用redis.async_connection.set(key, b1, nxTrue, exsettings.reconcile.redis_lock_ttl_seconds)实现每 config 互斥拿不到锁抛SsoReconcileLockBusyError19314Celery 任务吞掉该异常仅打 warning 不重试finally中释放锁即使异常也保证释放TTL 兜底防 worker 卡死。TestLockSemantics四条用例分别验证并发同 config 一去重一 19314、不同 config 锁互不干扰、异常释放、SETNXex1800断言。4.5 AC-05/06/07Relink 子系统Schemaschemas/relink.pyRelinkRequest{old_external_ids, matching_strategy: Literal[external_id_map,path_plus_name], external_id_map, sourcesso, dry_run}响应含applied / would_apply / conflicts三段服务relink_service.pyexternal_id_map策略按显式映射直接改写dept.external_idpath_plus_name策略在同 source 未占用 external_id 的 dept 中按(path, name)精确匹配单候选自动 apply多候选写入RelinkConflictStore并返回 conflictsdry_runtrue只收集would_apply不写库策略不识别抛 19315apply 写audit_log.actiondept.relink_applied冲突存储relink_conflict_store.pyRedis Hashrelink_conflict:{dept_id}字段为候选new_external_id值为 JSONTTL 7 天relink_conflict_ttl_seconds604800忘记解决的冲突自动过期重跑 relink 即重建——比建表更轻量且无需清理 cron手工解决resolve_conflict(dept_id, chosen_new_external_id)校验所选在候选集内否则 19316改写 external_id 写dept.relink_resolvedaudit 删除 store 条目HTTP 层api/endpoints/relink.py两个 POST 路由都Depends(verify_hmac)F014 复用并列入TENANT_CHECK_EXEMPT_PATHS豁免租户中间件服务层以ROOT_TENANT_ID bypass_tenant_filter运行——这是无 JWT 内部调用的正确姿势。4.6 AC-12周报与每日升级TsConflictReporter.weekly_report聚合过去 7 天org_sync_log中levelwarn AND event_typets_conflict的事件按 external_id 计数 weekly_conflict_threshold(3)的条目进入 flagged通过 F011 复用的list_global_super_admin_ids()send_inbox_notice发站内消息payload 含窗口天数、总冲突数、每个 flagged external_id 及suggested_action: run /api/v1/internal/departments/relink建议并写conflict_weekly_sentmarker 行daily_escalation_report检查该 marker 距今是否 ≥5 天daily_escalation_days5且冲突仍活跃满足则每日 09:00 升级告警。查询依赖org_sync_log复合索引idx_conflict_lookup (level, event_type, external_id, create_time)——spec.md 明确若 v2.5.0/F009 建表无此索引本 feature 的 DDL 补丁需ALTER TABLE ... ADD INDEX并补event_type VARCHAR(32) NOT NULL DEFAULT 、level、external_id、source_ts BIGINT四列迁移见 org_sync.py 模型 中的event_type字段与acreate_eventDAO 方法。五、手工 QA 指引复现验证环境自动化无法覆盖的时间窗口类6h Beat、周一 09:00 周报与UI 交互类全局超管站内消息交给手工 QA。原文验证指引整理如下。5.1 环境准备# 进入仓库后端目录 cd src/backend # 本地如有 BiShengVENV conda env conda activate BiShengVENV uv sync --frozen --python $(which python) # 数据库迁移F014 → F015 .venv/bin/alembic upgrade head # 验证 org_sync_log 新字段 .venv/bin/python -c from sqlalchemy import inspect from bisheng.core.database import engine print(sorted(c[name] for c in inspect(engine).get_columns(org_sync_log))) # 期待输出含 event_type / level / external_id / source_ts5.2 自测命令速查# 单文件 .venv/bin/pytest test/org_sync/test_org_reconcile_service.py -v .venv/bin/pytest test/department/test_department_relink_service.py -v .venv/bin/pytest test/celery/test_reconcile_celery_tasks.py -v .venv/bin/pytest test/department/test_relink_api_integration.py -v .venv/bin/pytest test/department/test_relink_conflict_store.py -v .venv/bin/pytest test/org_sync/test_remote_dept_differ.py -v # 全 F015 回归 .venv/bin/pytest test/ -k reconcile or relink or org_sync -v # 迁移往返验证可逆 .venv/bin/alembic upgrade head .venv/bin/alembic downgrade -1 .venv/bin/alembic upgrade head5.3 端到端手工验证脚本(A) AC-01 手工触发 6h 校对不必真等 6 小时# 启动 worker beat .venv/bin/celery -A bisheng.worker.main:bisheng_celery worker -Q knowledge_celery -l info .venv/bin/celery -A bisheng.worker.main:bisheng_celery beat -l info # 手动派发一次 .venv/bin/python -c from bisheng.worker.org_sync.reconcile_tasks import reconcile_all_organizations reconcile_all_organizations.delay() # 检查 org_sync_log 近 1 小时事件 .venv/bin/python -c import asyncio, datetime from bisheng.org_sync.domain.models.org_sync import OrgSyncLogDao async def run(): rows await OrgSyncLogDao.aget_conflicts_since(datetime.datetime.utcnow()-datetime.timedelta(hours1)) print(frecent events: {len(rows)}) asyncio.run(run()) (B) AC-05/06/07 relink HTTPHMAC 签名 curlpython3 PY import hmac, hashlib secret btest-hmac-secret-ABC123 # 同 sso_sync.gateway_hmac_secret method, path POST, /api/v1/internal/departments/relink body b{old_external_ids:[DEPT-OLD], matching_strategy:path_plus_name, dry_run:true} print(hmac.new(secret, f{method}\n{path}\n.encode() body, hashlib.sha256).hexdigest()) PY # 用上面输出的签名 curl -X POST http://localhost:7860/api/v1/internal/departments/relink \ -H X-Signature: sig -H Content-Type: application/json \ -d {old_external_ids:[DEPT-OLD], matching_strategy:path_plus_name, dry_run:true}(C) AC-11 手工构造同 ts 冲突-- 预置 deptGateway 已写入 E1ts7000 UPDATE department SET last_sync_ts7000, is_deleted0 WHERE sourcefeishu AND external_idE1; -- 让 Celery reconcile 在 ts7000 时 diff 出 archive移除 remote 中 E1 即可然后触发 -- .venv/bin/python -c from bisheng.worker.org_sync.reconcile_tasks import reconcile_single_config; reconcile_single_config.delay(config_id) -- 检查 audit_log 是否写入 SELECT action, target_id, metadata FROM audit_log WHERE actiondept.sync_conflict ORDER BY create_time DESC LIMIT 5; -- 检查 org_sync_log 事件行 SELECT event_type, level, external_id, source_ts, create_time FROM org_sync_log WHERE event_typets_conflict AND external_idE1 ORDER BY create_time DESC LIMIT 5;(D) AC-12 手工触发周报告.venv/bin/python -c import asyncio from bisheng.org_sync.domain.services.ts_conflict_reporter import TsConflictReporter summary asyncio.run(TsConflictReporter.weekly_report()) print(summary) # 然后检查全局超管的站内消息message 表六、发版前专项AC-08 十万部门压测AC-08 是唯一不进 CI的验收项由 scripts/performance/locust_ldap_reconcile_100k.py占位发版前 2 周落地执行。计划如下fake provider 生成 10 万RemoteDepartmentDTO2 层树100 个一级 × 1000 个二级在本地 MySQL Redis 跑OrgReconcileService.reconcile_config度量 wall time目标 30 min、MySQL CPU / 连接数峰值、Redis 命令吞吐峰值、Worker 内存峰值基线回填到压测基线表。压测基线发版前填写指标目标实测10 万部门 reconcile wall time 30 minTBDMySQL 峰值 CPU 70%TBDMySQL 峰值连接数 100TBDRedis 命令峰值 QPS 5kTBDWorker 峰值内存 1 GBTBD七、E2E 执行记录HMAC 网关与两个真实 bug/e2e-test执行记录2026-04-20Claude Opus 4.7 lilu114 独立 7861 uvicornHMAC secrettest-114-hmac-secret-v16/6 passed同时暴露并修复 2 个真实 bugrelink_service/reconcile_service未 wrapbypass_tenant_filter ROOT_TENANT_ID→ 所有无 JWT 的内部调用报 20004 Missing tenant context修复见 4.2/4.5 中手工安装租户上下文的代码路径reconcile_tasks.reconcile_single_config的except SsoReconcileLockBusyError永不 match——http_exception()返回的是 FastAPIHTTPException需要改为捕获原始异常类当前源码 reconcile_service.py 的_acquire_lock仍以http_exception()抛出Celery 侧捕获逻辑需与之一致。E2E 覆盖矩阵本次运行测试方法AC结果TestHmacGate::test_relink_without_signature_rejectedHMAC gate✅TestHmacGate::test_relink_with_invalid_signature_rejectedHMAC gate✅TestHmacGate::test_resolve_conflict_without_signature_rejectedHMAC gate✅TestRelinkDelegation::test_ac05_external_id_map_strategy_round_tripAC-05✅TestRelinkDelegation::test_ac06_path_plus_name_strategy_round_tripAC-06✅TestRelinkDelegation::test_ac07_dry_run_never_writesAC-07✅E2E 范围说明聚焦 HTTP 层 wiringHMAC dep router service delegation 契约深度行为same-ts 冲突、pathname 候选评分、UserTenantSyncServicefan-out由 64 条单元/集成测覆盖AC-016h Beat与 AC-12周报按 skill 规则归为UI 交互类 / 时间窗口类手工验证不在 E2E 自动化范围。八、回归验证合并前最后一次全量回归命令# 在仓库后端目录运行 .venv/bin/pytest test/ -k permission or tenant or fga or sso or org_sync or relink or reconcile -v期待 F002/F011/F012/F013/F014 全部通过——F015 的 60 条测试必须在其依赖的五个前序 feature 之上保持绿灯这从侧面验证了OrgSyncTsGuardF014、DepartmentDeletionHandlerF011、UserTenantSyncServiceF012等跨 feature 契约的稳定性。九、验证方法论小结回看ac-verification.md这套验证体系有三个可复用的要点AC → 任务 → 测试的三级映射每条 AC 精确落到 tasks.md 的任务编号Txx与具体测试类/方法形成可审计的覆盖闭环任何未覆盖项一眼可见自动化与手工分工明确纯逻辑diff、ts 决策、SETNX用单元/集成测锁死时间窗口与 UI 交互Beat 周期、周报、站内消息留给带-c脚本的手工 QA规模化性能10 万部门单独列为发版前专项不进 CIE2E 聚焦契约而非重复深度HTTP 层只验 HMAC 门禁 路由 服务委派深度行为交给下层测试避免 E2E 重复覆盖。对任何需要定时校对 外部系统换型重建映射的同步类功能这套自动化矩阵 手工 QA 脚本 发版前压测专项 E2E 契约验证的组合拳都值得直接借鉴。【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表