feat: 接入飞书组织架构同步 + 外网部署参数 Web 化 - #110
Open
lwl3520 wants to merge 137 commits into
Open
Conversation
- User 新增 Phone / FeishuUserID / LastSyncAt 三个字段 - Group 新增 FeishuDeptID / FeishuParentID 两个字段 - sendEmail 改 variadic 签名,支持邮件附件(向后兼容) - 新增 EmailAttachment 类型
- 新增 FeishuSyncLog 模型(同步日志表) - 新增 FeishuClient + tenant_access_token 缓存 + 限流 + 指数退避重试 - 新增飞书 API 封装(部门树、用户列表、用户详情) TODO: 当前是自定义 HTTP 客户端实现,下次提交改为 larksuite/oapi-sdk-go/v3 官方 SDK。 larksuite/oapi-sdk-go/v3 v3.9.9 与 golang.org/x/time v0.15.0 已作为间接依赖加入。
用 larksuite/oapi-sdk-go/v3 替代自定义 HTTP 客户端,去掉手写的 token 缓存/限流/重试,由 SDK 内置处理。 - FeishuClient 薄封装 lark.Client,暴露 WalkDepartments / ListUsersInDept / GetUser / TestConnection - 保留 FeishuDept/FeishuUser 值类型(指针解引用),让同步引擎不接触 SDK 细节 - 移除 golang.org/x/time 依赖,larksuite SDK 升为直接依赖
RunSync 编排:拉部门树 → 映射 VPN 组 → 拉成员去重 → 逐用户 reconcile → 离职检测。 - FeishuSyncer.RunSync:全局互斥防重叠,每次同步落盘一行 FeishuSyncLog - reconcileGroups:飞书部门拓扑序映射为 VPN Group(父先于子),根部门成员归默认组 - reconcileUser:新增/更新分离;字段归属用显式 allowlist,绝不触碰管理员字段(MfaSecret/IpAddr/ExpireDate 等) - 复职检测:leaver→active 时重新生成密码、强制改密、补发邮件 - detectLeavers:仅改 is_enable,保留证书与历史 - ResendWelcome:供 admin UI 发送邮件按钮复用 - 密码生成:mobile 后 6 位 + 4 位 crypto/rand 大小写字母 - ensureClientCert/sendWelcomeEmail:复用 genclient shell 与 email.html,邮件附 .ovpn
- config.go: 新增 SysFeishuConfig 结构、viper 默认值、9 个包级运行时变量 - loadConfig 中 AES 解密 app_secret 到明文变量(与 email.password 同容忍策略) - OnConfigChange 回调增加 restartFeishuCron,改 cron 表达式或开关无需重启 - main.go: cron 提为包级全局 cronScheduler,新增 restartFeishuCron(nil 容错) - AutoMigrate 增加 FeishuSyncLog - 新增 currentFeishuConfig() 组装运行时配置供 handler 复用
- main.go: 新增 4 个 admin 路由(同步/测试/日志/重发邮件) - /settings POST 增加 feishu_app_secret AES 加密分支 - feishu.go: ResendWelcome/sendWelcomeEmail 重构为包级函数,重发邮件不依赖飞书启用
- index.html: 系统设置 tab 新增飞书同步配置段 + 同步日志表 - settings.js: 字段 load/bind、测试连接、立即同步、日志渲染 - user.js: 账号管理表'更多'菜单加'发送邮件'项及 handler - email.html: 新增 HasAttachment 附件提示块 - main.go: 两处 email 模板调用点补 HasAttachment 字段(向后兼容)
覆盖密码生成格式/回退/唯一性、手机号规整、数字剥离、用户名派生优先级。6 个用例全通过。 注意:package main 测试会触发 init()→initConfig() 写 config.json, 运行时需 OVPN_DATA 指向临时目录(config.json 已在 .gitignore)。
实测发现多数员工 personal email 字段为空、enterprise_email 有值。 - FeishuUser 增加 EnterpriseEmail,SDK 解析两处补齐 - 新增 primaryEmail():个人邮箱优先否则企业邮箱 - deriveUsername 优先级 email→企业邮箱→手机后6→user_id→open_id - reconcileUser 创建/更新/复职三处 Email 字段与发邮件均用 primaryEmail - smoke test 验证字段结构通过;单元测试同步更新
- build/Dockerfile.local: 多阶段构建,golang 编译 openvpn-web + alpine 运行;easy-rsa 用宿主机预下载包;sed 修 CRLF - docker-compose.local.yml: 一键 up --build - .gitignore: 忽略 easy-rsa 二进制包
NAT/端口映射等外网暴露场景,让生成的客户端 .ovpn 指向真实外网可达地址。 genclient 的 remote 行优先级:显式参数 > 环境变量 OVPN_REMOTE_ADDR/OVPN_REMOTE_PORT > 自动探测本机出网IP/config端口。 实测验证:设 OVPN_REMOTE_ADDR=203.0.113.10 OVPN_REMOTE_PORT=11194, 生成的 ovpn remote 行 = '203.0.113.10 11194';传显式参数则覆盖环境变量。
问题2: 账户没和同名 ovpn 绑定 - reconcileUser 创建用户时设 OvpnConfig = username + '.ovpn' - 老用户回填:更新路径仅当 OvpnConfig 为空时补同名配置(绝不覆盖管理员已设值) 问题1: 重置密码发邮件偶发失败 - 根因是 Docker 内嵌 DNS(127.0.0.11) 解析 smtp.feishu.cn 瞬时 'server misbehaving' - docker-compose.local.yml 加 dns: [223.5.5.5, 8.8.8.8] 用公共 DNS 根治
把 OVPN_REMOTE_ADDR/PORT/EMAIL_LOGIN_LINK/HELP_URL/SITE_URL 等部署参数从纯环境变量收拢到 Web 系统管理菜单,环境变量仅作首次初始默认值写入 config.json(之后 Web 菜单为准)。
config.go:
- SysEmailConfig 新增 LoginLinkEnabled / HelpURL
- OvpnConfig 新增 OvpnRemoteAddr / OvpnRemotePort(取代 shell 直接读环境变量)
- viper.SetDefault 读 OVPN_SITE_URL / OVPN_EMAIL_LOGIN_LINK / OVPN_HELP_URL / OVPN_REMOTE_ADDR / OVPN_REMOTE_PORT 作为初始值
- 加 envOr / envOrBool helper
docker-entrypoint.sh genclient:
- 改为从 config.json 读 ovpn_remote_addr/port(不再读环境变量,配置收敛到 Web)
email.html:
- 登录链接段包 if LoginLinkEnabled
- 加 {{ if .HelpUrl }} 使用说明链接段
- 附件提示文案根据 LoginLinkEnabled 条件渲染(关闭时不提点击上方链接登录后下载)
调用点(feishu.go / main.go)三处邮件渲染均传 LoginLinkEnabled + HelpUrl。
Web 菜单:
- 邮件配置段:邮件带登录链接 checkbox + 使用说明链接 text
- openvpn 段:外显IP + 外显端口(NAT 场景)
- settings.js:4 个新字段的 load + blur/change handler
- 4 个新提示符补齐问号 path
data/ 含 PKI、客户端配置、ovpn.db、config.json 等运行时数据,gitignored。
Windows 工作区下 build/openvpn-auth 被 git autocrlf 转成 CRLF,而 Dockerfile.local 的 sed 只处理了 entrypoint 和 supervisord,漏掉了 openvpn-auth。镜像内该认证脚本 shebang 变为 #!/bin/bash\r 无法执行, OpenVPN auth-user-pass-verify 静默失败:所有 VPN 账号登录失败,web 登录正常。 - Dockerfile.local: sed 补上 /usr/lib/openvpn/plugins/openvpn-auth - 新增 .gitattributes 强制脚本类文件保持 LF,防止复发
根部门 ID、同步计划、默认分组 ID 三处 tooltip 图标原本只画了外圈 <path>,缺少内部问号形状的 <path>,渲染出来是一个空圈、看不到问号。 补全后三处均与同模板中其他 bi-question-circle 保持双 path 结构。
main.go 大量 time.Now().Format(...) 直接使用 time.Time 自带 Location,
未调 .Local(),因此最终输出取决于宿主机 /etc/localtime。云上默认 UTC
的宿主机,Web 上证书过期时间、飞书同步日志、CSV 导出等所有时间戳都
不是东八区。
修复:
- main.go init() 最前面用 time.LoadLocation("Asia/Shanghai") 覆盖
time.Local,进程内所有 time.Now() 自动按东八区输出。
必须放在 initConfig/loadConfig 之前;package-level var(如 logger
prefix)初始化在 init 之前完成,那一刻取到的字符串字面量是 UTC 的一
次性前缀,对后续运行无影响。
- docker-compose*.yml 添加 TZ=Asia/Shanghai 环境变量,让容器内非 Go
进程(openvpn/easyrsa/supervisord)的日志时间戳也是东八区。双保险。
上一版只改了 main.go init() 调用 time.LoadLocation("Asia/Shanghai"),
但 Dockerfile.local 的 apk add 列表里没装 tzdata。Alpine 镜像里 musl
libc 自带的 zoneinfo 只有 UTC 等基础项,没有 Asia/Shanghai,
time.LoadLocation 报错、time.Local 退回 UTC,所有时间仍然慢 8 小时。
修复:
- Dockerfile.local 的 apk add 加上 tzdata,镜像里就有
/usr/share/zoneinfo/Asia/Shanghai,LoadLocation 成功。
- main.go init() 在 LoadLocation 失败时不再静默跳过,而是往 stderr 打
WARNING 日志,提示检查 Dockerfile 是否装了 tzdata,避免再次踩坑。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
新增飞书(Lark)通讯录同步功能,可按 cron 周期自动拉取部门树与员工,本地开账号、签证书、邮件发送 .ovpn 附件 + 登录链接。同时把外网部署相关参数从纯环境变量收拢到 Web 系统管理菜单。
主要变更
飞书组织架构同步
larksuite/oapi-sdk-go/v3),DFS 遍历部门树 + 分页拉成员is_enable=false),保留证书和历史,方便复职复用外网部署参数 Web 化
config.json,之后以 Web 菜单为准):openvpn.ovpn_remote_addr,环境变量OVPN_REMOTE_ADDR)openvpn.ovpn_remote_port,环境变量OVPN_REMOTE_PORT)system.base.site_url,环境变量OVPN_SITE_URL)system.email.login_link_enabled,环境变量OVPN_EMAIL_LOGIN_LINK,默认开)system.email.help_url,环境变量OVPN_HELP_URL)genclient改为从config.json读外显地址(不再依赖 shell 环境变量,配置收敛到 Web)修复
OvpnConfig(用户页面下载/登录校验的绑定键)OvpnConfig为空时补同名配置,绝不覆盖管理员已设值223.5.5.5/8.8.8.8),根治 Docker 内嵌 DNS 解析外部域名瞬时server misbehaving(表现为 SMTP 发邮件偶发失败)数据库变更
GORM AutoMigrate 自动处理,无破坏性:
user表新增phone/feishu_user_id(带索引)/last_sync_at字段group表新增feishu_dept_id(带索引)/feishu_parent_id(带索引)字段feishu_sync_log表(同步日志)system.email.login_link_enabled/system.email.help_url配置项openvpn.ovpn_remote_addr/openvpn.ovpn_remote_port配置项默认行为兼容
system.feishu.feishu_enabled=false),需在 Web 菜单填 AppID/AppSecret 后手动启用feishu_user_id的账号)本地构建/测试
附:
build/Dockerfile.local(多阶段本地编译)、docker-compose.local.yml(一键启动)、scripts/feishu-smoke/(飞书 API 连通性独立诊断工具)。已知事项 / 待办
history_max_days加feishu_log_max_days)验证清单(review 时可参考)
data/clients/<user>.ovpn已生成且remote行指向正确地址IsEnable=false,.ovpn仍存在IsEnable=true、新密码、新邮件.ovpn中remote行 = 该地址