docs: 更新 CLAUDE.md,补充人员架构与组织同步说明
- 修正构建服务器 IP(192.168.42.134 → 192.168.40.206,以部署脚本为准) - 新增上游报表库说明:花名册来自公司报表库而非钉钉通讯录,含关键表与状态码含义 - 新增人员架构与组织同步章节:数据模型、在职判定口径、离职联动、归属与署名分离 - 补充部署前置条件:所需环境变量、主机指纹、必须在内网执行 - 记录 2026-08-13 的三个迁移与数据操作结果 - 已知问题补充 5 条,含级联删除风险与 .env 特殊字符陷阱 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7b0ee5efb4
commit
ce44459685
144
CLAUDE.md
144
CLAUDE.md
@ -44,8 +44,9 @@ npm run lint # ESLint
|
||||
|
||||
**物理服务器:**
|
||||
- 生产服务器:`192.168.58.20`
|
||||
- 构建服务器:`192.168.42.134`
|
||||
- 构建服务器:`192.168.40.206`(部署脚本内的固定值;旧文档中的 `192.168.42.134` 已不是部署入口)
|
||||
- 香港公网服务器:`103.118.245.6`(FRP 入口 + Nginx 反向代理)
|
||||
- 上游报表库:`218.93.9.21`(SQL Server 2019,人事与组织架构数据源)
|
||||
|
||||
**公网访问架构:**
|
||||
- 域名:`sc.jiansun.vip`(素材平台)、`s3.jiansun.vip`(MinIO)
|
||||
@ -62,6 +63,102 @@ npm run lint # ESLint
|
||||
- `gongxiangpan-redis`(生产 Redis)
|
||||
- `youyidian-3d-shadow`(影子环境应用)
|
||||
|
||||
### 上游报表库(人事与组织架构数据源)
|
||||
|
||||
⚠️ **重要认知**:花名册**不是**从钉钉通讯录来的。钉钉在本项目中只负责登录鉴权与消息推送,从未调用过通讯录接口。人事权威源是公司报表库。
|
||||
|
||||
**连接信息(只读账号,配置在 `.env`,勿写入仓库):**
|
||||
- Server:`218.93.9.21`
|
||||
- Database:`JSReportReplica`
|
||||
- Schema:`rpt`(只读)
|
||||
- 环境变量:`REPORT_DB_SERVER` / `REPORT_DB_DATABASE` / `REPORT_DB_USER` / `REPORT_DB_PASSWORD`
|
||||
|
||||
⚠️ 密码含 `#` 等特殊字符时,在 `.env` 中**必须加引号**,否则 `#` 之后会被当作注释截断,表现为登录失败。
|
||||
|
||||
**关键表:**
|
||||
|
||||
| 表 | 行数量级 | 用途 |
|
||||
|---|---|---|
|
||||
| `rpt.hr_groups` | 1075 | 组织架构树。仅 `Id` / `parent_id` / `name` 三列可用,无状态位、无排序、无编码。单一根节点(Id=1),零孤儿节点 |
|
||||
| `rpt.hr_employee` | 1296 | 钉钉员工表,含 `manager_userid`、`dept_id_list`、`active` |
|
||||
| `rpt.hr_employee_register` | 1296 | 入职登记,含 `sys01-employeeStatus` 状态码、`sys00-flowerName` 花名 |
|
||||
| `rpt.hr_flowerName_book` | — | 完整员工档案(含社保、银行卡等敏感信息,谨慎查询) |
|
||||
| `rpt.hr_employee_dimission` | 4711 | **离职表**,含 `last_work_day`、`handover_userid` 交接人 |
|
||||
|
||||
**员工状态码:`2` 试用、`3` 正式、`5` 已离职。**注意上游还存在中文值(正式/离职/试用)与空值,口径混杂,不可用关键词匹配判断在职。
|
||||
|
||||
---
|
||||
|
||||
## 人员架构与组织同步(2026-08-13 重构)
|
||||
|
||||
### 核心原则
|
||||
|
||||
**钉钉唯一值是全项目唯一的人员身份判定依据。** 姓名、花名、邮箱只能用于人工排错,不得自动建立绑定。
|
||||
|
||||
### 数据模型
|
||||
|
||||
| 模型 | 作用 |
|
||||
|---|---|
|
||||
| `UserEmployeeBinding` | 素材库账号 ↔ 权威人员档案的正式绑定。活跃绑定双向唯一(部分唯一索引,仅约束 `status='ACTIVE'`) |
|
||||
| `OrganizationUnit` | 共享组织架构,直接镜像 `hr_groups`。上游删除的部门置 `isActive=false` 而非物理删除 |
|
||||
| `PerformanceSurveyEmployeeProfile` | 事实上的平台级人员主档(表名仍带历史前缀)。新增 `employmentState`、`resignedAt`、`handoverDingtalkUserId` |
|
||||
|
||||
`users.dingtalkUserId` 已加唯一约束,数据库层面杜绝一人多号。
|
||||
|
||||
### 关键文件
|
||||
|
||||
| 文件 | 用途 |
|
||||
|---|---|
|
||||
| `lib/employee-directory.ts` | **通用人员目录服务**。上层统一通过它访问人员数据,不直接读带 `PerformanceSurvey` 前缀的模型 |
|
||||
| `lib/organization-sync.ts` | 组织架构同步、在职状态校正、离职联动、每日定时任务 |
|
||||
| `lib/material-transfer.ts` | 素材正式转移与部门托管 |
|
||||
|
||||
### 在职状态判定
|
||||
|
||||
**不要用关键词匹配 `employmentStatus`**(上游原样值,口径混杂)。使用规范化的 `employmentState`:
|
||||
|
||||
- `ACTIVE` / `RESIGNED` / `UNKNOWN`
|
||||
- `UNKNOWN` 视为在职——上游查不到明确状态的人宁可多留,误判导致"人凭空消失"比"多出几个人"难发现
|
||||
- 判定条件统一走 `getActivePerformanceSurveyEmployeeProfileWhere()` 或 `getActiveEmployeeWhere()`
|
||||
|
||||
### 离职联动
|
||||
|
||||
上游标记离职后自动执行:归档素材库账号 + 把素材托管给所在部门。
|
||||
|
||||
- **默认只处理离职满 30 天者**(`RESIGNATION_MIN_DAYS`),避开交接期、返聘与上游数据延迟
|
||||
- 离职日期缺失者暂缓,交人工核实
|
||||
- 超级管理员账号不由自动流程处理
|
||||
|
||||
### 素材归属与署名分离
|
||||
|
||||
**这是刻意的设计,不是数据不一致:**
|
||||
|
||||
- `Material.userId`(归属)+ `authorName`(署名):素材是谁做的,永远记在谁名下
|
||||
- `Material.custodyDepartmentId`(托管部门):人离职后由部门接手维护,**不改 userId 与 authorName**
|
||||
- 首页卡片显示的作者读的是 `user.realName || username`,托管不影响显示
|
||||
|
||||
### 定时同步
|
||||
|
||||
`ensureDailyOrganizationSyncStarted()` 在考核调研上下文中懒启动,每 24 小时执行:组织架构 → 在职状态 → 离职联动。
|
||||
|
||||
⚠️ **生产容器必须配置 `REPORT_DB_*` 环境变量**,否则同步连不上上游,人员档案会退化成用素材库信息拼的兜底假档案(无工号、无职级、`isLeadership=false`)。
|
||||
|
||||
### 运维脚本
|
||||
|
||||
```bash
|
||||
# 人员架构盘点(纯只读,可重复执行)
|
||||
npx tsx scripts/diagnose-people-architecture.ts
|
||||
|
||||
# 组织架构与在职状态同步(默认预演,--apply 才写入)
|
||||
npx tsx --env-file=.env scripts/sync-organization.ts
|
||||
npx tsx --env-file=.env scripts/sync-organization.ts --apply
|
||||
|
||||
# 离职人员素材部门托管
|
||||
npx tsx scripts/custody-archived-user-materials.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 数据库配置
|
||||
|
||||
**PostgreSQL (192.168.58.20:5432):**
|
||||
@ -246,12 +343,32 @@ ssh root@192.168.58.20 "cat /root/youyidian-3d/.env 2>/dev/null || echo 'No .env
|
||||
|
||||
**当前部署流程:**
|
||||
1. 源码先提交并推送到 Git 仓库
|
||||
2. 构建服务器 (192.168.42.134) 增量拉取指定提交
|
||||
3. 构建服务器构建 Docker 镜像并推送到本地镜像仓库 (192.168.42.134:5000)
|
||||
2. 构建服务器 (192.168.40.206) 增量拉取指定提交
|
||||
3. 构建服务器构建 Docker 镜像并推送到本地镜像仓库 (192.168.40.206:5000)
|
||||
4. 生产服务器 (192.168.58.20) 拉取指定提交镜像
|
||||
5. 替换正式容器或影子容器
|
||||
6. 检查 `/api/health`,失败时默认尝试回滚旧镜像
|
||||
|
||||
**⚠️ 部署前置条件(缺一不可):**
|
||||
|
||||
脚本由维护人员执行,需要以下环境变量,且**必须在内网环境**(构建机 192.168.40.206 走公网穿透连不上,表现为 TCP 端口"可达"但 SSH 握手失败——穿透代理对任何连接都返回成功,端口连通性测试是假阳性):
|
||||
|
||||
```powershell
|
||||
$env:BUILD_SERVER_PASSWORD = '构建机密码' # 脚本用 plink -pw,不走 SSH 密钥
|
||||
$env:BUILD_SERVER_HOST_KEY = 'SHA256:...' # 已人工核对的构建机指纹
|
||||
$env:PROD_SERVER_HOST_KEY = 'SHA256:...' # 已人工核对的生产机指纹
|
||||
```
|
||||
|
||||
已记录的主机指纹(来自 `~/.ssh/known_hosts`,生产机已现场验证一致):
|
||||
- 生产机 192.168.58.20:`SHA256:b0qukGVPuI8j+lykfCIplkcKWh7d4Pq9APzvr4FDnss`
|
||||
- 构建机 192.168.40.206:`SHA256:MXW5w48SFSTUaqS+dS1G9hY228hI6SLepjhCmItHL04`
|
||||
|
||||
正式部署必须显式指定 40 位提交 SHA,脚本不接受"分支最新提交":
|
||||
|
||||
```powershell
|
||||
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
|
||||
```
|
||||
|
||||
`deploy-remote-build.ps1` 属于本地历史脚本名,仅作为识别应急资料使用,不是仓库交付入口,也不再作为默认部署入口。旧的“本机打包源码 -> 上传到构建机”链路不再作为当前标准口径。
|
||||
|
||||
详细部署规范见:
|
||||
@ -566,6 +683,11 @@ catch (error) {
|
||||
10. **构建缓存** - 默认保留构建缓存加快构建,只在必要时使用 `-AggressiveBuildCacheCleanup`
|
||||
11. **Alpine 镜像源** - 使用清华源,避免官方源在国内网络环境下的连接问题
|
||||
12. **音频/文件访问路由** - `.dockerignore` 和部署脚本中排除 `uploads` 时要精确,避免误伤 `app/api/uploads` 路由
|
||||
13. **人员身份判定** - 只认钉钉唯一值。钉钉登录遇到同名账号会**拒绝并转人工**,不再自动合并——这是有意的摩擦,防止重名变成越权
|
||||
14. **在职状态** - 不要用关键词匹配 `employmentStatus`,用 `employmentState`。上游有数字码、中文、空值三种口径并存
|
||||
15. **素材署名与归属分离** - 署名和归属对不上通常是**正确的**(素材转过手,署名保留原作者),不要"修正"成一致
|
||||
16. **`Material.user` 是级联删除** - 一旦有代码真的删除 User,会连带硬删其全部素材。目前全项目无硬删除调用,靠"没人碰"撑着而非数据库拦着;引入离职下线流程前必须先改掉
|
||||
17. **上游连接密码含特殊字符** - `.env` 中含 `#` 的值必须加引号,否则被当注释截断
|
||||
|
||||
### 最近修复的问题(2026-04-30 至 2026-05-05)
|
||||
|
||||
@ -599,6 +721,22 @@ catch (error) {
|
||||
- 历史数据回填:2730 条 SKU 的 `white_background_uploaded_at` 字段(使用资产最后创建时间)
|
||||
- 历史数据默认值:`white_background_source_type` 统一回填为"渲染"
|
||||
|
||||
**2026-08-13:人员架构统一到钉钉唯一值**
|
||||
|
||||
执行了三个迁移(均已在生产库 `lobechat` 应用):
|
||||
- `20260813_add_user_employee_binding`:绑定表 + `users.dingtalkUserId` 唯一约束
|
||||
- `20260813_add_material_custody_department`:素材托管部门
|
||||
- `20260813_add_organization_units_and_employment_state`:组织架构表 + 规范化在职状态
|
||||
|
||||
数据操作结果:
|
||||
- 建立 503 条账号↔人员绑定,覆盖活跃账号的 96.3%(余下 18 个无钉钉 ID,需人工确认)
|
||||
- 同步 1075 个部门,单根树 5 层,零孤儿节点
|
||||
- 校正在职状态:1285 在职 / 219 离职 / 214 未知(此前 216 条已离职档案中有 215 条被误判为在职)
|
||||
- 离职联动:归档 139 个账号(离职满 30 天者),39 人暂缓
|
||||
- 素材部门托管:3775 条,孤儿素材清零
|
||||
- 回填 1807 条被误存为邮箱的作者名(全部来自任务-详情入口的历史缺陷)
|
||||
- 账号治理:处置 1 个"组织上查无此人"的 ADMIN 账号(素材移交后撤权归档)
|
||||
|
||||
---
|
||||
|
||||
## 👤 开发者偏好
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user