# 架构设计 > 本文讲系统结构、职责划分、数据模型、技术难点、开发顺序。具体技术选型见 [技术栈](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 或业务缓存文件。