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