feat: feishu 离职卡片 + 快照 runner(快照→镜像→离职流程)

快照段(飞书→SQLite 镜像→离职双确认推进)落地,不碰 LLDAP:
- feishu 新增离职确认卡片:自定义机器人 webhook 发送 interactive 卡片,
  确认按钮 url 跳程序自己的一次性端点;本地假 webhook 单测验证载荷与错误处理
- config.ConfirmURL/ResignConfirmRoute:确认端点路由与 URL 拼接(server/syncer 共用避免循环)
- mapping.FlatDeptInfo:部门树拍平为拼音点分命名并计算 DN(纯函数),
  供快照落库持久化 pinyin_name/dn,物化与 WebUI 复用
- SnapshotRunner.RunSnapshot:拉飞书→事务写镜像→离职生命周期 reconcile
  (机器确认→token+卡片→pending_confirm;节流防刷屏;重新可见撤销并作废 token)
- 链路单测:快照落库、离职机器确认、发卡、状态机转移、节流、重新入职全部通过
This commit is contained in:
2026-09-29 23:24:39 +08:00
parent 48b0ea35fc
commit f997e48a75
6 changed files with 1036 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
package config
import (
"net/url"
"strings"
)
// ResignConfirmRoute 是一次性离职确认端点的路由。
//
// 它被两处引用:server 用它注册路由(且此路由不经 Auth 中间件,token 即凭证),
// 快照 runner 用它拼接飞书卡片按钮的跳转 URL。放在 config 包而非 server/feishu,
// 是为了避免 server↔syncer 的循环依赖。
const ResignConfirmRoute = "/api/resign/confirm"
// ConfirmURL 用对外 base-url、uid 与一次性 token 拼出确认端点地址。
//
// token 内嵌在链接里,因此该链接必须具备一次性、过期等约束——这正是
// store 的 ResignConfirmToken 提供的语义,而不是仅靠链接难以猜测。
func ConfirmURL(baseURL, uid, token string) string {
q := url.Values{}
q.Set("uid", uid)
q.Set("token", token)
return strings.TrimRight(baseURL, "/") + ResignConfirmRoute + "?" + q.Encode()
}