commit e8711037601f2494346ccf52ff9aeace1c2043da Author: QiuSW Date: Mon Jun 22 22:23:17 2026 +0800 docs: initialize harness coding docs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4232c3c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +# AGENTS.md + +> Codex / AI coding agent 的仓库级入口。进入本仓库后,先读本文,再读 `docs/00-ai-start-here.md`。 + +## 项目定位 + +Dingding HRM 是一个 Go + Gin 本地 Web 应用,用于同步、浏览、搜索和导出钉钉多站点部门与人员数据。 + +本项目从旧 Python 脚本迁移而来。旧脚本只负责从钉钉 OpenAPI 拉取 JSON 文件;新项目要提供 SQLite 持久化、REST API、原生前端页面和可手动触发的数据同步。 + +## Harness Coding 文档入口 + +本项目后续编码所需的 harness coding 文档都在 `docs/` 目录中。Codex 开始编程时,不需要查找 `README.md` 或历史分析文件,直接按下面顺序读取 `docs/` 即可。 + +| 文档 | 用途 | +| --- | --- | +| `docs/00-ai-start-here.md` | AI coding agent 的启动入口、阅读顺序、任务领取规则 | +| `docs/01-vision.md` | 项目目标、用户、产品原则、非目标 | +| `docs/02-requirements.md` | MVP 需求、用户故事、验收标准 | +| `docs/03-tech-stack.md` | Go + Gin、SQLite、原生前端等技术选型 | +| `docs/04-architecture.md` | 系统结构、职责边界、数据模型、开发顺序 | +| `docs/05-coding-rules.md` | 写代码前必须遵守的硬规则 | +| `docs/06-tasks.md` | 分阶段任务看板,每轮只领取一个任务 | +| `docs/api.md` | REST API 合约、错误格式、分页筛选约定 | +| `docs/routes.md` | 原生前端页面路由和模块归属 | +| `docs/current-state.md` | 当前代码现实、可运行命令、下一步任务 | + +## 必读顺序 + +每次开始工作前,按顺序读取: + +1. `AGENTS.md` +2. `docs/00-ai-start-here.md` +3. `docs/05-coding-rules.md` +4. `docs/current-state.md` +5. 与当前任务相关的具体文档 + +如果准备写代码,还必须查看 `docs/06-tasks.md`,只领取第一个状态为 `TODO` 且依赖均完成的任务。 + +## 技术硬约束 + +- 后端使用 Go + Gin。 +- 数据库使用 SQLite。 +- 前端不使用 Vue、React、Svelte、Angular、Element Plus、Vite 等框架或构建链。 +- 前端使用原生 HTML/CSS/JavaScript,静态文件由 Gin 托管。 +- 钉钉 OpenAPI 只能由后端调用,浏览器端不得直接请求钉钉接口。 +- 密钥、token、真实手机号等敏感数据不得写入代码或文档样例。 + +## 工作规则 + +- 需求、API、数据模型或路由变化时,先更新 `docs/` 中对应文档。 +- 代码实现必须服从 `docs/03-tech-stack.md`、`docs/04-architecture.md` 和 `docs/api.md`。 +- 不要引入旧方案中的 Vue / chi 技术栈;最终约束以 `docs/` 为准。 +- 不要在未确认的情况下引入前端依赖、后台任务库或 ORM。 +- 每轮只做一个任务,做完后更新 `docs/06-tasks.md` 和 `docs/current-state.md`。 + +## 验证 + +当前尚未初始化代码。初始化后至少维护这些命令: + +```powershell +go test ./... +go build ./... +``` + +如果前端只是静态文件,验证应包含: + +- Gin 启动后 `/` 可访问。 +- `/api/health` 返回正常。 +- 页面能通过同源 `/api/*` 请求后端。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..774bfad --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,49 @@ +# CLAUDE.md + +> Claude Code 的仓库级上下文。进入本仓库后,先读本文,再读 `docs/00-ai-start-here.md`。 + +## 仓库用途 + +本仓库用于实现 Dingding HRM:一个用 Go + Gin 编写的钉钉部门和人员浏览、搜索、同步管理工具。 + +项目不使用前端框架。Gin 后端既提供 REST API,也托管原生前端静态文件。 + +## Harness Coding 文档入口 + +本项目后续 Claude Code 编程所需的 harness coding 文档都在 `docs/` 目录中。不要依赖根目录 `README.md` 或历史分析文件作为入口。 + +| 文档 | 用途 | +| --- | --- | +| `docs/00-ai-start-here.md` | AI coding agent 的启动入口、阅读顺序、任务领取规则 | +| `docs/01-vision.md` | 项目目标、用户、产品原则、非目标 | +| `docs/02-requirements.md` | MVP 需求、用户故事、验收标准 | +| `docs/03-tech-stack.md` | Go + Gin、SQLite、原生前端等技术选型 | +| `docs/04-architecture.md` | 系统结构、职责边界、数据模型、开发顺序 | +| `docs/05-coding-rules.md` | 写代码前必须遵守的硬规则 | +| `docs/06-tasks.md` | 分阶段任务看板,每轮只领取一个任务 | +| `docs/api.md` | REST API 合约、错误格式、分页筛选约定 | +| `docs/routes.md` | 原生前端页面路由和模块归属 | +| `docs/current-state.md` | 当前代码现实、可运行命令、下一步任务 | + +## 关键事实 + +- 新项目技术事实以 `docs/03-tech-stack.md` 为准。 +- 新项目架构、数据库和目录边界以 `docs/04-architecture.md` 为准。 +- API 合约以 `docs/api.md` 为准。 +- 页面与静态前端结构以 `docs/routes.md` 为准。 + +## 工作方式 + +1. 先确认当前任务是写文档、初始化代码、实现接口、实现页面,还是做同步逻辑。 +2. 写代码前按 `docs/00-ai-start-here.md` 的顺序读取上下文。 +3. 从 `docs/06-tasks.md` 领取一个任务,不跳步。 +4. 修改后运行对应验证命令,并在回复中说明结果。 +5. 如果发现文档和代码现实冲突,优先更新 `docs/current-state.md` 并说明冲突。 + +## 不要做 + +- 不要使用 Vue、React、Element Plus 或 Vite。 +- 不要让浏览器直接访问钉钉 OpenAPI。 +- 不要把真实 app_key、app_secret、access_token、手机号写入仓库。 +- 不要从旧 JSON 文件中推断未记录的字段含义。 +- 不要一次性生成完整大系统;按任务看板小步实现。 diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md new file mode 100644 index 0000000..25f90fb --- /dev/null +++ b/docs/00-ai-start-here.md @@ -0,0 +1,96 @@ +# AI 开发入口 + +> 给 AI coding agent 的项目入口。这里负责导航和流程,硬性编码规则见 [`05-coding-rules.md`](05-coding-rules.md)。 + +## 一句话定位 + +Dingding HRM 是一个 Go + Gin + SQLite 本地 Web 应用,用于同步、浏览、搜索和导出钉钉多站点部门与人员数据。 + +第一版 MVP 只做:多站点配置、手动全量同步、部门树浏览、人员搜索筛选、JSON/CSV 导出、Gin 托管原生前端。 + +## 必读顺序 + +每次开始写代码前,按这个顺序建立上下文: + +1. [`01-vision.md`](01-vision.md):为什么做、为谁做、什么不做。 +2. [`02-requirements.md`](02-requirements.md):MVP 要什么、怎么算达成。 +3. [`03-tech-stack.md`](03-tech-stack.md):既定技术选型。 +4. [`04-architecture.md`](04-architecture.md):系统结构、职责划分、数据模型和关键难点。 +5. [`05-coding-rules.md`](05-coding-rules.md):写代码前必须遵守的规则。 +6. [`06-tasks.md`](06-tasks.md):领取本轮唯一任务。 +7. [`current-state.md`](current-state.md):当前代码现实、可运行命令、下一步任务。 + +仓库根目录的 `AGENTS.md`、`CLAUDE.md` 也必须先读。仓库级规则优先。 + +## 当前阶段 + +当前项目处于:MVP 起步,尚未初始化 Go 代码。 + +优先路径: + +1. Phase 0:Go + Gin + SQLite + 静态文件托管的最小可运行地基。 +2. Phase 1:验证钉钉 token、部门、用户接口封装。 +3. Phase 2:实现同步入库和查询 API。 +4. Phase 3:实现原生前端部门浏览、人员搜索、导出。 +5. Phase 4:验收、打包、运行文档。 + +## 领取任务规则 + +从 [`06-tasks.md`](06-tasks.md) 领取任务时: + +- 只领取第一个状态为 `TODO` 且依赖均为 `DONE` 的任务。 +- 开始前把该任务状态改为 `DOING`。 +- 本轮只完成这一个任务。 +- 验收通过后把状态改为 `DONE`。 +- 做完即停,汇报验证结果,等待下一步指令。 + +如果代码实际状态和任务看板冲突,先说明冲突,不要擅自跳步。 + +## MVP 边界 + +MVP 只做: + +- 站点配置管理,保存多站点 app_key / app_secret。 +- 手动全量同步部门和人员数据。 +- 后端缓存并刷新 access_token。 +- 部门树浏览。 +- 人员列表、关键词搜索、部门筛选、分页。 +- JSON/CSV 导出。 +- 原生 HTML/CSS/JavaScript 前端,由 Gin 托管。 + +MVP 不做: + +- 登录、多用户权限、审计审批。 +- 钉钉回调事件订阅。 +- 复杂组织变更历史追踪。 +- 前端框架、组件库或独立前端构建链。 +- 云部署、Docker、分布式任务队列。 + +## 事实来源 + +项目事实只信: + +- `docs/` 中已定稿的需求、技术栈、架构、API 和路由。 +- 后续代码中的数据库迁移和模型定义。 +- 实现过程中通过钉钉 OpenAPI 官方响应和本地测试确认的字段事实。 + +不要把以下内容当事实来源: + +- 未导入仓库的旧 Python 源码猜测。 +- 临时 JSON 导出中未被文档确认的字段含义。 + +## 验证命令 + +当前尚未初始化代码。初始化后维护这些命令: + +```powershell +go test ./... +go build ./... +``` + +说明: + +- 改后端后跑:`go test ./...`。 +- 改构建或入口后跑:`go build ./...`。 +- 改静态前端后至少启动 Gin 并人工访问 `/` 与相关 `/api/*`。 +- 如果命令当前不可运行,回复里必须说明原因。 diff --git a/docs/01-vision.md b/docs/01-vision.md new file mode 100644 index 0000000..299aa4d --- /dev/null +++ b/docs/01-vision.md @@ -0,0 +1,40 @@ +# 项目愿景 + +## 一、核心目标 + +Dingding HRM 要解决:现有钉钉组织和人员数据只能通过 Python 脚本导出 JSON,缺少集中浏览、搜索、同步和导出的管理界面。 + +> 让内部管理人员能够在一个本地 Web 工具中查看多站点钉钉部门架构和人员信息,并快速搜索、筛选、导出。 + +它不是完整 HR 系统,也不是钉钉替代品,而是一个围绕钉钉组织通讯录数据的轻量管理工具。 + +## 二、目标用户 + +- 内部管理员:配置站点密钥、触发同步、查看同步状态。 +- HR / 行政人员:按站点、部门、姓名、手机号、工号查找人员。 +- 开发维护者:维护钉钉 API 适配、数据库迁移、导出逻辑和本地部署。 + +## 三、产品原则 + +- 核心查询优先:先保证部门树、人员列表、搜索筛选可靠。 +- 数据真实优先:以钉钉 OpenAPI 返回和 SQLite 入库结果为准,不用演示数据冒充真实数据。 +- 本地单体优先:MVP 用单个 Go 服务完成 API、同步、静态前端托管。 +- 低依赖优先:不引入前端框架,不引入复杂后台任务系统。 +- 密钥安全优先:真实 app_secret、token 不进入代码、文档样例或日志明文。 + +## 四、核心价值主张 + +| 价值点 | 说明 | +| --- | --- | +| 集中查看 | 把梅州、东莞、合肥等站点的组织和人员数据放入统一界面 | +| 快速检索 | 支持按姓名、手机号、工号、部门筛选人员 | +| 可控同步 | 管理员可手动同步钉钉数据,并查看成功或失败状态 | +| 简单部署 | Go 单体服务托管 API 和静态页面,减少前后端分离复杂度 | + +## 五、不做什么(非目标) + +- 不做人事档案全生命周期管理。 +- 不做薪酬、考勤、审批、绩效等 HR 模块。 +- 不做复杂权限系统,MVP 默认本地受信环境使用。 +- 不做前端框架版本,不引入 Vue / React / Element Plus。 +- 不在浏览器端直接调用钉钉 OpenAPI。 diff --git a/docs/02-requirements.md b/docs/02-requirements.md new file mode 100644 index 0000000..d4f2034 --- /dev/null +++ b/docs/02-requirements.md @@ -0,0 +1,75 @@ +# 需求 + +> 本文只描述要什么与怎么算达成,用产品 / 用户语言表达。技术方案、数据结构、字段定义见 [架构设计](04-architecture.md)。 + +## 一、业务现状 + +| 项 | 状态 | +| --- | --- | +| 用户 | 内部管理员、HR / 行政人员需要查看多站点钉钉组织和人员数据 | +| 数据 | 旧 Python 脚本可从钉钉 OpenAPI 拉取部门、部门用户 ID、用户详情,并保存为 JSON | +| 现有系统 | 当前仓库尚未有应用代码;旧项目位于 `D:\PythonP\snippets\DingtalkHRM` | +| 约束 | 钉钉 OpenAPI 有 CORS 和密钥安全限制,必须由后端代理调用 | + +## 二、用户角色 + +- 管理员:配置站点、维护 app_key / app_secret、触发同步、查看同步日志。 +- 查询用户:浏览部门树、搜索人员、导出当前人员数据。 +- 未登录用户:MVP 暂不做登录,默认运行在本地或受信内网。 + +## 三、功能清单 + +### 第一版 MVP(最小闭环) + +| 功能 | 用户能做什么 | 优先级 | +| --- | --- | --- | +| 站点配置 | 新增、修改、删除梅州 / 东莞 / 合肥等站点配置 | P0 | +| 手动全量同步 | 对指定站点拉取部门和人员详情并写入 SQLite | P0 | +| 部门浏览 | 选择站点后查看部门树,点击部门查看成员 | P0 | +| 人员搜索筛选 | 按姓名、手机号、工号关键词搜索,按部门和状态筛选 | P0 | +| 分页列表 | 以表格分页展示人员核心字段 | P0 | +| 数据导出 | 导出当前站点或筛选结果为 JSON / CSV | P0 | +| 同步日志 | 查看最近同步时间、状态和错误信息 | P0 | + +### 后续迭代 + +| 功能 | 描述 | 阶段 | +| --- | --- | --- | +| 定时同步 | 按配置周期自动同步站点数据 | V2 | +| 登录权限 | 增加管理员账号和访问控制 | V2 | +| 增量同步 | 优先同步变更数据,减少 OpenAPI 调用 | V3 | +| 变更历史 | 记录部门和人员字段变化 | V3 | + +## 四、核心用户故事(MVP) + +1. 作为管理员,我打开系统后能看到已配置站点和每个站点的同步状态。 +2. 我可以选择一个站点并点击同步,系统会拉取钉钉部门和人员数据。 +3. 我可以打开部门页面,看到树形部门结构,并查看某个部门下的人员。 +4. 我可以在人员页面输入姓名、手机号或工号,快速定位员工。 +5. 我可以导出当前站点或当前筛选条件下的人员数据。 +6. 当钉钉接口失败或密钥错误时,系统会展示失败状态和可读错误信息。 + +## 五、验收标准(MVP) + +- 站点配置:新增站点后刷新页面仍存在;密钥字段不在列表页明文展示。 +- 手动同步:点击同步后生成同步日志;成功后部门和人员数据可查询;失败时保存错误信息。 +- 部门浏览:选中站点后展示部门层级;点击部门后右侧人员列表随之过滤。 +- 人员搜索筛选:关键词、部门、状态、分页参数可组合使用;结果数量和列表一致。 +- 数据导出:JSON 返回 UTF-8 JSON;CSV 可用 Excel 打开且包含表头。 +- 静态前端:访问 `/` 可打开应用;所有前端请求使用同源 `/api/*`。 + +## 六、范围边界与决策 + +| 问题 | 决策 | +| --- | --- | +| 第一版平台 | 本地 / 内网 Web 应用 | +| 是否需要账号 | MVP 不做登录,后续再评估 | +| 第一版范围 | 站点配置、同步、浏览、搜索、导出 | +| 暂不支持 | 前端框架、云部署、多用户权限、钉钉回调 | + +## 七、待确认 / 风险点 + +- 钉钉接口配额:需要在同步服务中记录错误和失败位置,避免静默丢数据。 +- 部门与用户关系:用户可能属于多个部门,MVP 需保留 `dept_id_list` 原始 JSON 字段。 +- 旧 JSON 迁移:是否需要导入旧 JSON 作为初始数据,待实现同步前确认。 +- 密钥存储:MVP 先存 SQLite,后续可评估本机加密或环境变量覆盖。 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md new file mode 100644 index 0000000..6d38bb0 --- /dev/null +++ b/docs/03-tech-stack.md @@ -0,0 +1,54 @@ +# 技术栈(Tech Stack) + +> “用什么”的统一速查表。选型与理由在此集中维护;“怎么把它们搭起来”见 [架构设计](04-architecture.md)。 + +## 一、技术栈一览 + +| 维度 | 选型 | 状态 | 理由 / 说明 | +| --- | --- | --- | --- | +| 前端框架 | 无,原生 HTML/CSS/JavaScript | 已定 | 降低构建复杂度,由 Gin 直接托管 | +| UI 样式方案 | 手写 CSS | 已定 | MVP 页面数量少,避免引入组件库 | +| 状态管理 | 浏览器内存状态 + URL 查询参数 | 已定 | 站点、部门、筛选条件简单可控 | +| 后端 | Go + Gin | 已定 | 用户指定;路由、中间件和静态文件托管成熟 | +| 数据库 | SQLite | 已定 | 本地单体应用,无需独立数据库服务 | +| SQLite 驱动 | `modernc.org/sqlite` 优先 | 待验证 | 纯 Go,Windows 构建更简单;如遇兼容问题再评估 `mattn/go-sqlite3` | +| 鉴权方式 | MVP 无账号 | 已定 | 默认本地或受信内网运行 | +| 钉钉 API 客户端 | Go `net/http` | 已定 | 标准库足够,便于测试和超时控制 | +| 部署方式 | 单个 Go 进程 + SQLite 文件 + 静态文件 | 已定 | 简单可复制 | +| 测试 | `go test ./...` | 已定 | 覆盖服务层、数据层、handler | + +## 二、决策记录与演进 + +- 不引入前端框架:当前页面是管理工具界面,原生 DOM 和 fetch 足够完成 MVP。 +- 使用 Gin 托管前端:避免前后端两个开发服务器和 CORS 配置。 +- SQLite 作为唯一持久化:站点配置、token、部门、人员、同步日志都入库。 +- 不引入 ORM:优先使用 `database/sql` 和小型 repository,避免隐藏 SQL 行为。 +- 定时任务 V2 再做:MVP 先实现手动同步和 token 按需刷新。 + +## 三、构建与运行命令 + +当前尚未初始化代码,以下是目标命令: + +| 用途 | 命令 | +| --- | --- | +| 初始化依赖 | `go mod tidy` | +| 本地开发 | `go run .` | +| 构建 | `go build ./...` | +| 测试 | `go test ./...` | +| 格式化 | `gofmt -w .` | + +Windows PowerShell: + +```powershell +go mod tidy +go run . +go test ./... +go build ./... +``` + +## 四、依赖纪律 + +- 新增第三方依赖前,先说明用途、替代方案和维护成本。 +- 不允许引入 Vue、React、Element Plus、Vite、Webpack 等前端框架或构建链。 +- 不允许同一职责并存两套路由、数据库访问或状态管理方案。 +- 钉钉 SDK 不是默认选择;除非标准 HTTP 封装无法满足需求。 diff --git a/docs/04-architecture.md b/docs/04-architecture.md new file mode 100644 index 0000000..eaa1ac9 --- /dev/null +++ b/docs/04-architecture.md @@ -0,0 +1,185 @@ +# 架构设计 + +> 本文讲系统结构、职责划分、数据模型、技术难点、开发顺序。具体技术选型见 [技术栈](03-tech-stack.md)。 + +## 一、系统结构 + +```text +浏览器 + | + | GET /, /static/* + | fetch /api/* + v +Go + Gin 单体服务 + | | + | +-- 托管 web/ 静态前端文件 + | + +-- REST handlers + +-- services: 钉钉 API、同步、导出 + +-- repositories: SQLite 读写 + | | + | +-- SQLite 数据库 + | + +-- 钉钉 OpenAPI +``` + +## 二、职责划分 + +**原生前端** + +- 渲染站点选择、同步状态、部门树、人员表格、筛选表单。 +- 使用 `fetch` 调用同源 `/api/*`。 +- 只保存当前页面交互状态,不保存密钥和 token。 +- 不直接访问钉钉 OpenAPI。 + +**Gin 后端** + +- 托管 `web/` 静态文件。 +- 提供 REST API。 +- 负责请求参数校验、错误响应、分页与导出。 +- 封装钉钉 OpenAPI 调用、token 获取与复用。 +- 执行同步流程并记录同步日志。 + +**SQLite / repository** + +- 持久化站点配置、access token、部门、人员、同步日志。 +- 保存钉钉返回的关键字段和必要原始 JSON 字段。 +- 提供按站点、部门、关键词、状态分页查询。 + +## 三、数据模型 + +### 3.1 站点与 token + +```sql +CREATE TABLE sites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + app_key TEXT NOT NULL, + app_secret TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE access_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + site_id INTEGER NOT NULL REFERENCES sites(id) ON DELETE CASCADE, + token TEXT NOT NULL, + expires_in INTEGER NOT NULL, + fetched_at INTEGER NOT NULL, + created_at TEXT NOT NULL +); +``` + +### 3.2 部门 + +```sql +CREATE TABLE departments ( + site_id INTEGER NOT NULL REFERENCES sites(id) ON DELETE CASCADE, + dept_id INTEGER NOT NULL, + parent_id INTEGER NOT NULL, + name TEXT NOT NULL, + raw_json TEXT, + updated_at TEXT NOT NULL, + PRIMARY KEY (site_id, dept_id) +); + +CREATE INDEX idx_departments_site_parent ON departments(site_id, parent_id); +``` + +### 3.3 人员 + +```sql +CREATE TABLE users ( + site_id INTEGER NOT NULL REFERENCES sites(id) ON DELETE CASCADE, + userid TEXT NOT NULL, + name TEXT, + mobile TEXT, + title TEXT, + job_number TEXT, + active INTEGER, + avatar TEXT, + unionid TEXT, + dept_id INTEGER, + dept_id_list TEXT, + dept_name TEXT, + hired_date INTEGER, + raw_json TEXT, + updated_at TEXT NOT NULL, + PRIMARY KEY (site_id, userid) +); + +CREATE INDEX idx_users_site_name ON users(site_id, name); +CREATE INDEX idx_users_site_mobile ON users(site_id, mobile); +CREATE INDEX idx_users_site_job_number ON users(site_id, job_number); +CREATE INDEX idx_users_site_dept ON users(site_id, dept_id); +``` + +### 3.4 同步日志 + +```sql +CREATE TABLE sync_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + site_id INTEGER REFERENCES sites(id) ON DELETE SET NULL, + sync_type TEXT NOT NULL, + status TEXT NOT NULL, + message TEXT, + started_at TEXT NOT NULL, + finished_at TEXT +); +``` + +## 四、关键技术难点 + +| 难点 | 说明 | 应对 | +| --- | --- | --- | +| token 过期 | access_token 有有效期,接口失败可能由 token 过期导致 | 按需获取;过期前刷新;失败日志可见 | +| 部门递归 | 需要从根部门 `dept_id=1` 拉完整树 | 先实现 BFS/队列同步,并测试重复和空子部门 | +| 用户多部门 | 用户可能属于多个部门,单 `dept_id` 不足 | 保留 `dept_id_list` 原始 JSON,MVP 用主部门筛选 | +| 密钥安全 | app_secret 需要存储但不能泄露 | 列表和日志不返回密钥;导出不包含密钥 | +| 静态前端复杂度 | 原生 JS 容易膨胀 | 按页面拆分 `web/assets/*.js`,保持小模块 | + +## 五、推荐开发顺序 + +1. 初始化 Go module、Gin、健康检查、静态文件托管。 +2. 建立 SQLite 连接、迁移和基础 repository。 +3. 实现站点 CRUD API。 +4. 实现钉钉 token、部门、用户 API client。 +5. 实现全量同步入库和同步日志。 +6. 实现部门、用户、导出 API。 +7. 实现原生前端页面。 +8. 完整验收和运行文档。 + +## 六、项目结构建议 + +```text +dingding_hrm/ +├── docs/ +├── main.go +├── go.mod +├── internal/ +│ ├── config/ +│ ├── db/ +│ ├── dingtalk/ +│ ├── handler/ +│ ├── model/ +│ ├── repository/ +│ └── service/ +├── web/ +│ ├── index.html +│ └── assets/ +│ ├── app.js +│ └── style.css +├── data/ +│ └── dingding_hrm.db +├── AGENTS.md +└── CLAUDE.md +``` + +## 七、架构纪律 + +- `handler` 不直接拼 SQL,不直接调用钉钉 OpenAPI。 +- `service` 编排业务流程,但不处理 HTTP 细节。 +- `repository` 只负责 SQLite 读写和事务。 +- `dingtalk` 包只负责 OpenAPI 请求、响应解析和错误归一。 +- 前端只调用 `/api/*`,不保存密钥、token 或业务缓存文件。 diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md new file mode 100644 index 0000000..1903c88 --- /dev/null +++ b/docs/05-coding-rules.md @@ -0,0 +1,80 @@ +# 编码规则(Coding Rules) + +> 每次写代码前先读完本文件。这是让 AI 不跑偏、代码质量稳定的硬约束。 + +## 0. 黄金法则 + +1. 不臆造:钉钉字段、接口、依赖、文件,不确定就查证或询问。 +2. 守范围:只做当前任务要求的事,不顺手加 V2 功能。 +3. 照架构:使用 Go + Gin + SQLite + 原生前端,不擅自换栈。 +4. 小步改:一次只解决一个任务,不夹带无关重构。 +5. 可验证:改完必须能构建、能测试、对得上验收标准。 + +## 1. 动手前 + +- 按链路确认:`vision` -> `requirements` -> `tech-stack` -> `architecture` -> `tasks`。 +- 找到本任务对应的验收标准,写之前就知道怎么算做对。 +- 先找现有函数、包、SQL、页面和测试,复用优先。 +- 如果需求含糊,或改动会偏离 Go + Gin / 原生前端约束,先问。 + +## 2. 事实来源纪律 + +- 钉钉接口以 `docs/api.md`、`docs/04-architecture.md` 和实际 OpenAPI 响应为准。 +- 新项目技术事实以 `docs/03-tech-stack.md` 为准。 +- API 形状以 `docs/api.md` 为准。 +- 数据结构变化必须同步更新 `docs/04-architecture.md`、`docs/api.md` 和相关任务。 +- 不从旧 JSON 文件中推断未确认的字段含义。 + +## 3. 范围纪律 + +- MVP 只做 `02-requirements.md` 中列为 P0 的功能。 +- V2 / V3 功能只记录,不实现。 +- 不实现登录、权限、定时同步、变更历史,除非任务明确要求。 +- 不为将来可能用到提前抽象。 + +## 4. 架构纪律 + +- 后端必须用 Gin。 +- 前端必须是原生 HTML/CSS/JavaScript。 +- Gin 负责托管前端文件和 `/api/*`。 +- 不引入 Vue、React、Element Plus、Vite、Webpack。 +- 不让浏览器直接调用钉钉 OpenAPI。 +- 不把 SQL、钉钉调用和 HTTP 处理混在同一个函数里。 + +## 5. 代码规范 + +- Go 标识符使用英文,包名短小清晰。 +- UI 文案和文档默认中文。 +- 错误必须处理,不吞错。 +- HTTP 错误统一返回 `{"error":{"code":"...","message":"..."}}`。 +- 注释解释为什么,不复述做了什么。 +- Go 代码提交前运行 `gofmt`。 + +## 6. 测试与验证 + +完成前至少检查: + +- [ ] 构建通过。 +- [ ] 相关测试通过。 +- [ ] 对得上需求验收标准。 +- [ ] 没有夹带无关改动。 +- [ ] 涉及文档事实变化时,文档已同步。 +- [ ] 回复里如实说明跑了什么命令、结果如何。 + +目标命令: + +```powershell +go test ./... +go build ./... +``` + +## 7. 绝不 + +- 绝不把真实 app_key、app_secret、access_token、手机号样本写进代码或文档。 +- 绝不为了让测试通过而删除断言、降低验收标准。 +- 绝不擅自删除用户已有文件或重置工作区。 +- 绝不在没说明的情况下改公共接口、迁移数据结构或升级依赖。 + +## 8. 拿不准就问 + +问题要具体,说明卡在哪里、有哪些选项、倾向哪个选项以及原因。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md new file mode 100644 index 0000000..a6843b3 --- /dev/null +++ b/docs/06-tasks.md @@ -0,0 +1,76 @@ +# 任务看板(Tasks) + +> 把 MVP 拆成小步、可独立交付的任务,让 AI 一步一步开发,避免一次生成整个项目。 + +## 使用规则 + +1. 一次只做一个任务:每轮只领取一个状态为 `TODO`、且依赖均已 `DONE` 的任务,取最靠前的那个。 +2. 做完即停:完成该任务、自测通过、把状态改成 `DONE` 后,停下来汇报。 +3. 不跳步:依赖未完成的任务不能开工。 +4. 完成定义:以 [编码规则](05-coding-rules.md) 的验证清单为准。 +5. 动手前先读 `00-ai-start-here.md`、`05-coding-rules.md` 和 `current-state.md`。 + +## 状态图例 + +`TODO` 待开始 · `DOING` 进行中(同一时间最多 1 个)· `DONE` 已完成并验收 · `BLOCKED` 受阻(注明原因) + +--- + +## Phase 0 · 地基 + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-001 | 初始化 Go + Gin 项目骨架 | - | `go run .` 启动;`/api/health` 返回正常;`/` 返回静态首页 | TODO | +| T-002 | 建立配置、日志和目录结构 | T-001 | 目录符合 `04-architecture.md`;配置不含真实密钥;默认数据库路径可用 | TODO | +| T-003 | 建立 SQLite 迁移和基础测试 | T-002 | 表结构与架构文档一致;`go test ./...` 可运行 | TODO | + +## Phase 1 · 钉钉接口验证 + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-101 | 实现站点 CRUD API | T-003 | 可新增、查询、更新、删除站点;列表不返回明文 secret | TODO | +| T-102 | 实现钉钉 token client | T-101 | 用站点配置获取 token;错误响应可读;不记录 token 明文 | TODO | +| T-103 | 实现部门和用户 API client | T-102 | 能封装拉子部门、部门 userid、用户详情三个调用 | TODO | + +## Phase 2 · 同步与查询 API + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-201 | 实现全量同步服务 | T-103 | 从根部门拉部门树、用户详情并写入 SQLite;同步日志完整 | TODO | +| T-202 | 实现部门查询 API | T-201 | `/api/sites/{id}/departments` 返回可组树的数据 | TODO | +| T-203 | 实现人员查询 API | T-201 | 支持 keyword、dept_id、active、page、size | TODO | +| T-204 | 实现导出 API | T-203 | 支持 JSON 和 CSV;导出结果服从筛选参数 | TODO | +| T-205 | 实现统计和同步日志 API | T-201 | 首页可获得站点概览和最近同步结果 | TODO | + +## Phase 3 · 原生前端 + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-301 | 实现应用外壳和站点概览 | T-205 | `/` 展示站点、统计、同步状态和入口按钮 | TODO | +| T-302 | 实现站点配置页面 | T-101 | 可维护站点;secret 输入后不在列表明文展示 | TODO | +| T-303 | 实现部门浏览页面 | T-202, T-203 | 左侧部门树,右侧部门成员列表 | TODO | +| T-304 | 实现人员搜索页面 | T-203, T-204 | 关键词、筛选、分页、导出可用 | TODO | +| T-305 | 接入同步操作和状态反馈 | T-201, T-205, T-301 | 页面可触发同步并展示成功 / 失败 | TODO | + +## Phase 4 · 收尾与发布 + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-401 | 完整验收 MVP | T-305 | `02-requirements.md` 的 P0 验收全部通过 | TODO | +| T-402 | 完善运行和打包文档 | T-401 | `AGENTS.md`、`CLAUDE.md` 和 `docs/current-state.md` 写清运行方式;无真实密钥进入仓库 | TODO | + +## 里程碑 + +- M1:最小 Go + Gin + SQLite 地基完成。 +- M2:钉钉接口封装验证完成。 +- M3:同步、查询、导出 API 完成。 +- M4:原生前端 MVP 完成。 +- M5:可交付 / 可运行。 + +## 待办池(Backlog) + +- 定时同步。 +- 登录和权限。 +- 旧 JSON 数据导入。 +- 人员 / 部门变更历史。 +- 密钥本地加密存储。 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..8a25c54 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,199 @@ +# API 合约 + +> 本文定义 Go + Gin 后端 API 的目标形状。实现前可细化,但不要在代码里另起一套不兼容接口。 + +## 通用约定 + +- 传输:HTTP JSON,同源 `/api/*`。 +- 鉴权:MVP 无账号。 +- 请求体和响应体编码:UTF-8 JSON。 +- 时间格式:ISO 8601 字符串;钉钉原始时间戳字段保留原值。 +- 分页参数:`page` 从 1 开始,`size` 默认 20,最大 200。 + +通用错误响应: + +```json +{ + "error": { + "code": "bad_request", + "message": "请求参数不正确" + } +} +``` + +## 健康检查 + +### `GET /api/health` + +```json +{ + "ok": true +} +``` + +## 站点 + +### `GET /api/sites` + +返回站点列表。不得返回 `app_secret` 明文。 + +```json +{ + "items": [ + { + "id": 1, + "code": "meizhou", + "name": "梅州", + "has_secret": true, + "updated_at": "2026-06-22T21:00:00+08:00" + } + ] +} +``` + +### `POST /api/sites` + +```json +{ + "code": "meizhou", + "name": "梅州", + "app_key": "example", + "app_secret": "example" +} +``` + +### `PUT /api/sites/{site_id}` + +更新站点名称、app_key、app_secret。未提交的 secret 保持不变。 + +### `DELETE /api/sites/{site_id}` + +删除站点及其 token、部门、人员、同步日志。 + +## 同步 + +### `POST /api/sites/{site_id}/sync/full` + +触发指定站点全量同步。 + +成功响应: + +```json +{ + "log_id": 10, + "status": "success", + "departments": 120, + "users": 3500 +} +``` + +失败时返回 4xx / 5xx,并写入同步日志。 + +### `POST /api/sites/{site_id}/sync/token` + +手动刷新 token。响应不返回 token 明文。 + +```json +{ + "status": "success", + "expires_at": "2026-06-22T23:00:00+08:00" +} +``` + +### `GET /api/sync/logs` + +查询同步日志。参数:`site_id`、`status`、`page`、`size`。 + +## 部门 + +### `GET /api/sites/{site_id}/departments` + +返回平铺部门列表,前端组装树。 + +```json +{ + "items": [ + { + "dept_id": 1, + "parent_id": 0, + "name": "总部" + } + ] +} +``` + +## 人员 + +### `GET /api/sites/{site_id}/users` + +参数: + +- `keyword`:匹配姓名、手机号、工号。 +- `dept_id`:按主部门筛选。 +- `active`:`1` 或 `0`。 +- `page`:页码。 +- `size`:每页数量。 + +```json +{ + "items": [ + { + "userid": "u001", + "name": "张三", + "mobile": "138****0000", + "title": "工程师", + "job_number": "A001", + "dept_id": 1, + "dept_name": "研发部", + "active": 1 + } + ], + "page": 1, + "size": 20, + "total": 1 +} +``` + +### `GET /api/sites/{site_id}/users/{userid}` + +返回单个人员详情。默认不脱敏;如后续增加权限,再调整。 + +## 导出 + +### `GET /api/sites/{site_id}/export/users` + +参数: + +- `format`:`json` 或 `csv`。 +- 其余筛选参数同 `GET /users`。 + +响应: + +- `format=json`:`application/json; charset=utf-8` +- `format=csv`:`text/csv; charset=utf-8` + +## 统计 + +### `GET /api/stats` + +```json +{ + "sites": [ + { + "site_id": 1, + "site_name": "梅州", + "department_count": 120, + "user_count": 3500, + "last_sync_at": "2026-06-22T21:00:00+08:00", + "last_sync_status": "success" + } + ] +} +``` + +## 待实现时确认 + +- 手机号列表页是否默认脱敏。 +- CSV 字段顺序。 +- 同步是否需要防并发锁。 +- 删除站点前是否需要二次确认。 diff --git a/docs/current-state.md b/docs/current-state.md new file mode 100644 index 0000000..c03938d --- /dev/null +++ b/docs/current-state.md @@ -0,0 +1,67 @@ +# 当前实现状态 + +> 本文记录代码与任务看板的现实状态,帮助 AI coding agent 避免只看计划而忽略仓库现状。 + +## 当前快照 + +- 日期:2026-06-22 +- 阶段:MVP 起步 +- 技术栈:目标为 Go + Gin + SQLite + 原生 HTML/CSS/JavaScript +- 生产代码:尚未初始化 +- 测试:尚无 +- 数据:旧 JSON 数据未进入当前仓库;项目事实以 `docs/` 为准 + +## 当前目录要点 + +| 路径 | 状态 | 说明 | +| --- | --- | --- | +| `docs/` | 已有 | 当前项目规范化文档 | +| `main.go` | 待建 | Go 服务入口 | +| `internal/` | 待建 | 后端模块 | +| `web/` | 待建 | 原生前端静态文件 | +| `data/` | 待建 | SQLite 运行时数据目录 | + +## 任务看板状态 + +以 [`06-tasks.md`](06-tasks.md) 为准。 + +- 已完成:文档初始化。 +- 正在进行:无。 +- 下一个可领取任务:T-001 初始化 Go + Gin 项目骨架。 + +## 当前可运行内容 + +当前尚无可运行应用代码。 + +目标命令: + +```powershell +# 本地开发 +go run . + +# 测试 +go test ./... + +# 构建 +go build ./... +``` + +## 开始编码前检查 + +开始任意任务前: + +1. 读仓库级 agent 规则文件 `AGENTS.md`。 +2. 读 `docs/00-ai-start-here.md`。 +3. 读 `docs/05-coding-rules.md`。 +4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务。 +5. 将该任务状态改为 `DOING`。 + +## 维护规则 + +当实际代码状态发生变化时,同步更新本文件: + +- 新增或移动入口文件。 +- 初始化框架或模块。 +- 任务从 `TODO` 进入 `DOING` 或 `DONE`。 +- 新增可运行命令。 +- 发现文档和代码现实不一致。 diff --git a/docs/routes.md b/docs/routes.md new file mode 100644 index 0000000..ef9e7a4 --- /dev/null +++ b/docs/routes.md @@ -0,0 +1,86 @@ +# 路由与页面结构 + +> 本文约定原生前端页面路由、页面职责和组件归属。 + +## 页面路由 + +MVP 不使用前端路由框架。Gin 对以下路径都返回同一个 `web/index.html`,页面 JS 根据 `location.pathname` 渲染对应视图。 + +| 路由 | 页面 | MVP 说明 | +| --- | --- | --- | +| `/` | 仪表盘 | 展示站点概览、同步状态和快速入口 | +| `/sites` | 站点配置 | 管理站点 app_key / app_secret | +| `/departments` | 部门浏览 | 站点切换、部门树、部门成员 | +| `/users` | 人员搜索 | 搜索筛选、分页表格、导出 | +| `/logs` | 同步日志 | 查看同步记录和错误信息 | + +## 页面职责 + +### 仪表盘 + +- 展示每个站点的部门数、人员数、最后同步时间和状态。 +- 提供同步按钮。 +- 提供进入部门浏览、人员搜索、站点配置的入口。 + +### 站点配置 + +- 展示站点列表。 +- 新增、编辑、删除站点。 +- app_secret 输入后保存,但列表不明文展示。 + +### 部门浏览 + +- 左侧展示部门树。 +- 右侧展示当前部门下人员。 +- 支持站点切换。 +- 空部门、加载失败、未同步状态需要有明确提示。 + +### 人员搜索 + +- 支持站点切换。 +- 支持关键词、部门、状态筛选。 +- 支持分页。 +- 支持导出当前筛选结果。 + +### 同步日志 + +- 按站点和状态筛选日志。 +- 展示开始时间、结束时间、类型、状态、错误信息。 + +## 前端文件建议 + +```text +web/ +├── index.html +└── assets/ + ├── style.css + ├── app.js + ├── api.js + ├── state.js + ├── dashboard.js + ├── sites.js + ├── departments.js + ├── users.js + └── logs.js +``` + +## 组件建议 + +这里的组件是 JS 函数或小模块,不是框架组件。 + +| 模块 | 归属 | 说明 | +| --- | --- | --- | +| `renderShell` | 全局 | 页面框架、导航、站点选择入口 | +| `apiFetch` | 全局 | 封装 fetch、JSON 解析和错误展示 | +| `renderDashboard` | 仪表盘 | 渲染站点统计和同步按钮 | +| `renderSiteForm` | 站点配置 | 新增 / 编辑站点 | +| `renderDeptTree` | 部门浏览 | 将平铺部门列表组装为树 | +| `renderUserTable` | 部门 / 人员页 | 渲染分页人员表格 | +| `renderLogs` | 同步日志 | 渲染日志列表 | + +## 导航规则 + +- 顶部导航固定展示:仪表盘、站点配置、部门浏览、人员搜索、同步日志。 +- 站点选择应尽量保留在 URL 查询参数中,例如 `?site_id=1`。 +- 人员页筛选条件应保留在 URL 查询参数中,方便刷新后恢复。 +- API 失败时在页面内展示错误,不静默失败。 diff --git a/tasks.md b/tasks.md new file mode 100644 index 0000000..daf18cf --- /dev/null +++ b/tasks.md @@ -0,0 +1,132 @@ +# Dingding HRM 项目任务拆分 + +> 项目级任务总表。实际编码时仍按 `docs/00-ai-start-here.md` 和 `docs/06-tasks.md` 的规则:每轮只领取一个可执行任务,完成后更新状态与 `docs/current-state.md`。 + +## 状态说明 + +- `TODO`:待开始 +- `DOING`:进行中,同一时间最多一个 +- `DONE`:已完成并通过验证 +- `BLOCKED`:受阻,必须写明原因 + +## Phase 0 · 项目地基 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-001 | 初始化 Go module | - | `go.mod`、`go.sum` | `go test ./...` 可执行;模块名明确 | TODO | +| T-002 | 接入 Gin 最小服务 | T-001 | `main.go`、基础 router | `go run .` 启动;`GET /api/health` 返回 `{"ok":true}` | TODO | +| T-003 | 托管原生前端静态文件 | T-002 | `web/index.html`、`web/assets/style.css`、`web/assets/app.js` | 访问 `/` 返回首页;刷新 `/sites`、`/users` 不 404 | TODO | +| T-004 | 建立项目目录结构 | T-002 | `internal/config`、`internal/db`、`internal/handler`、`internal/service` 等目录 | 目录符合 `docs/04-architecture.md` | TODO | +| T-005 | 建立配置加载 | T-004 | 配置模块 | 支持默认端口、数据库路径;不包含真实密钥 | TODO | +| T-006 | 建立统一错误响应 | T-004 | handler 错误工具 | API 错误格式符合 `docs/api.md` | TODO | + +## Phase 1 · SQLite 数据层 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-101 | 接入 SQLite 驱动 | T-005 | DB 初始化代码 | 默认创建或打开 `data/dingding_hrm.db` | TODO | +| T-102 | 实现数据库迁移 | T-101 | migration 代码 | 建表 SQL 覆盖 sites、access_tokens、departments、users、sync_logs | TODO | +| T-103 | 实现站点 repository | T-102 | site repo | 支持增删改查;code 唯一约束生效 | TODO | +| T-104 | 实现 token repository | T-102 | token repo | 支持保存最新 token、查询未过期 token | TODO | +| T-105 | 实现部门 repository | T-102 | department repo | 支持按站点 upsert、清理、查询平铺列表 | TODO | +| T-106 | 实现人员 repository | T-102 | user repo | 支持 upsert、关键词查询、部门筛选、分页计数 | TODO | +| T-107 | 实现同步日志 repository | T-102 | sync log repo | 支持创建 running、更新 success/failed、分页查询 | TODO | +| T-108 | 数据层测试 | T-103 至 T-107 | repository tests | `go test ./...` 通过;覆盖关键约束和分页 | TODO | + +## Phase 2 · 站点与基础 API + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-201 | 实现站点列表 API | T-103 | `GET /api/sites` | 返回 items;不返回 `app_secret` 明文 | TODO | +| T-202 | 实现新增站点 API | T-103 | `POST /api/sites` | 参数校验;新增后可查询 | TODO | +| T-203 | 实现更新站点 API | T-103 | `PUT /api/sites/{site_id}` | 支持更新名称、key、secret;未提交 secret 时保持原值 | TODO | +| T-204 | 实现删除站点 API | T-103 | `DELETE /api/sites/{site_id}` | 删除站点级联清理关联数据 | TODO | +| T-205 | 站点 API 测试 | T-201 至 T-204 | handler tests | 覆盖成功、参数错误、重复 code、删除不存在 | TODO | + +## Phase 3 · 钉钉 OpenAPI Client + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-301 | 定义钉钉 client 接口 | T-004 | `internal/dingtalk` 接口和类型 | 方法覆盖 token、子部门、部门 userid、用户详情 | TODO | +| T-302 | 实现获取 access_token | T-301 | token HTTP 调用 | 支持 app_key/app_secret;错误信息可读 | TODO | +| T-303 | 实现获取子部门列表 | T-302 | department HTTP 调用 | 支持传入 `dept_id`;解析钉钉错误码 | TODO | +| T-304 | 实现获取部门 userid 列表 | T-302 | listid HTTP 调用 | 支持传入 `dept_id`;返回 userid 切片 | TODO | +| T-305 | 实现获取用户详情 | T-302 | user detail HTTP 调用 | 返回核心字段和 raw JSON | TODO | +| T-306 | 钉钉 client 单元测试 | T-302 至 T-305 | httptest tests | 使用 mock server 覆盖成功、钉钉错误、网络错误 | TODO | + +## Phase 4 · 同步服务 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-401 | 实现 token 按需获取服务 | T-104, T-302 | token service | 未过期复用;过期刷新;不向 API 返回 token 明文 | TODO | +| T-402 | 实现部门 BFS 同步 | T-105, T-303, T-401 | department sync | 从 `dept_id=1` 拉完整部门树;支持空子部门 | TODO | +| T-403 | 实现用户详情同步 | T-106, T-304, T-305, T-401 | user sync | 遍历部门 userid 并 upsert 用户详情 | TODO | +| T-404 | 实现全量同步事务边界 | T-402, T-403, T-107 | full sync service | 记录 running/success/failed 日志;失败可定位阶段 | TODO | +| T-405 | 实现同步防并发 | T-404 | sync lock | 同一站点并发同步被拒绝或排队,行为明确 | TODO | +| T-406 | 同步服务测试 | T-404 | service tests | mock 钉钉 client,覆盖成功、失败、部分空数据 | TODO | + +## Phase 5 · 查询、导出与统计 API + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-501 | 实现手动刷新 token API | T-401 | `POST /api/sites/{site_id}/sync/token` | 返回状态和过期时间;不返回 token | TODO | +| T-502 | 实现全量同步 API | T-404 | `POST /api/sites/{site_id}/sync/full` | 成功返回部门数、人员数、log_id | TODO | +| T-503 | 实现部门查询 API | T-105 | `GET /api/sites/{site_id}/departments` | 返回平铺部门列表 | TODO | +| T-504 | 实现人员查询 API | T-106 | `GET /api/sites/{site_id}/users` | 支持 keyword、dept_id、active、page、size | TODO | +| T-505 | 实现人员详情 API | T-106 | `GET /api/sites/{site_id}/users/{userid}` | 返回单人详情;不存在返回 404 | TODO | +| T-506 | 实现 JSON 导出 | T-106 | export service/API | 导出结果服从筛选条件 | TODO | +| T-507 | 实现 CSV 导出 | T-106 | export service/API | UTF-8 CSV,包含表头,Excel 可打开 | TODO | +| T-508 | 实现统计 API | T-105, T-106, T-107 | `GET /api/stats` | 返回每站点部门数、人员数、最近同步状态 | TODO | +| T-509 | 实现同步日志 API | T-107 | `GET /api/sync/logs` | 支持 site_id、status、page、size | TODO | +| T-510 | 查询与导出 API 测试 | T-503 至 T-509 | handler tests | 覆盖分页、筛选、空结果、导出 content-type | TODO | + +## Phase 6 · 原生前端基础 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-601 | 实现页面外壳 | T-003 | `web/index.html`、导航、布局 CSS | `/`、`/sites`、`/departments`、`/users`、`/logs` 可刷新访问 | TODO | +| T-602 | 实现前端 API 封装 | T-601 | `web/assets/api.js` | 统一处理 JSON、错误提示、下载响应 | TODO | +| T-603 | 实现前端状态和 URL 参数工具 | T-601 | `state.js` 或等价模块 | site_id、筛选参数可从 URL 恢复 | TODO | +| T-604 | 建立前端基础样式 | T-601 | `style.css` | 页面清晰、表格和表单可用、移动端不严重错位 | TODO | + +## Phase 7 · 原生前端功能页 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-701 | 实现仪表盘 | T-508, T-602 | dashboard view | 展示站点统计、最近同步状态、同步入口 | TODO | +| T-702 | 实现站点配置页 | T-201 至 T-204, T-602 | sites view | 可新增、编辑、删除站点;secret 不明文展示 | TODO | +| T-703 | 实现部门浏览页 | T-503, T-504, T-603 | departments view | 左侧部门树,右侧部门成员列表 | TODO | +| T-704 | 实现人员搜索页 | T-504, T-506, T-507, T-603 | users view | 支持关键词、部门、状态、分页、导出 | TODO | +| T-705 | 实现同步日志页 | T-509, T-602 | logs view | 支持按站点和状态筛选日志 | TODO | +| T-706 | 接入同步按钮和反馈 | T-502, T-701 | sync UI | 展示同步中、成功、失败状态;失败信息可读 | TODO | + +## Phase 8 · 集成验证与边界处理 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-801 | 完整 API 合约核对 | T-510 | API 对照记录 | 实现路径、参数、响应与 `docs/api.md` 一致 | TODO | +| T-802 | 完整页面路由核对 | T-706 | 路由对照记录 | 实现页面与 `docs/routes.md` 一致 | TODO | +| T-803 | 密钥和敏感信息检查 | T-706 | 检查记录 | 代码、日志、页面列表不泄露 secret/token | TODO | +| T-804 | 空数据和错误状态验收 | T-706 | UI/API 边界处理 | 未配置站点、未同步、钉钉失败都有明确提示 | TODO | +| T-805 | Windows 本地运行验收 | T-706 | 验收记录 | PowerShell 下可 `go run .`、访问页面、运行测试 | TODO | +| T-806 | 文档同步更新 | T-801 至 T-805 | 更新后的 docs | `docs/current-state.md`、`docs/06-tasks.md` 与代码现实一致 | TODO | + +## Phase 9 · 打包与交付 + +| ID | 任务 | 依赖 | 交付物 | 验收标准 | 状态 | +| --- | --- | --- | --- | --- | --- | +| T-901 | 实现生产构建方式 | T-805 | build 命令或脚本 | 可生成 Windows 可执行文件 | TODO | +| T-902 | 确认静态文件打包策略 | T-901 | 嵌入或随包分发方案 | 新目录运行时能访问 `/` 和 `/api/health` | TODO | +| T-903 | 完成交付说明 | T-902 | 更新 `AGENTS.md`、`CLAUDE.md`、`docs/current-state.md` | 新 agent 可快速找到运行、测试、构建方式 | TODO | +| T-904 | MVP 最终验收 | T-903 | 验收记录 | `docs/02-requirements.md` P0 全部通过 | TODO | + +## Backlog · 后续迭代 + +| ID | 任务 | 说明 | 状态 | +| --- | --- | --- | --- | +| B-001 | 定时同步 | 按站点配置周期自动同步 | TODO | +| B-002 | 登录和权限 | 管理员登录、查询用户权限区分 | TODO | +| B-003 | 旧 JSON 数据导入 | 从旧脚本导出的 JSON 初始化 SQLite | TODO | +| B-004 | 部门和人员变更历史 | 记录字段变化、支持对比 | TODO | +| B-005 | 密钥本地加密 | 使用本机安全存储或加密文件降低泄露风险 | TODO | +| B-006 | 操作审计 | 记录站点配置修改、同步触发、导出操作 | TODO |