hr-system/docs/01-design/architecture.md

244 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 迭代1总体设计 —— 系统架构与技术选型(hr-web)
- 版本:v1.1(迭代1-总体设计;v1.1 变更:database/api/ui 三份配套文档已按 ocai 口径重写为 v2.0,表数量与端点数量同步修正)
- 状态:评审通过(主agent评审,基线冻结)
- 需求基线:`docs/00-requirement/requirement-spec.md`(SRS v2)+ `docs/00-requirement/iteration1-function-detail.md`(F01~F15)
- 应用定义:`apps/hr-web.md`;模块定义:`modules/hr-org.md`、`hr-roster.md`、`hr-flow.md`、`hr-contract.md`、`hr-report.md`、`hr-system.md`
- 开发规范:ocai 技能集 —— web-application-spec、module-development-spec、database-table-definition-spec、crud-definition-spec、dspy-file-implementation-spec
## 0. 审核退回意见响应索引
PM 退回意见要求交付件至少包含 6 项内容,落点如下:
| # | 退回意见要求 | 落点文档/章节 |
|---|---|---|
| 1 | 系统总体架构与模块划分(hr-system、staff-mgr 职责边界) | 本文 §2、§4、§6 |
| 2 | 数据库设计(核心表、字段、索引、关系、迁移策略) | `database-design.md` 全文(§4 表结构、§5 索引策略、§7 迁移策略) |
| 3 | API 设计(路径、方法、请求/响应、鉴权、错误码) | `api-design.md` 全文(§1 通用约定、§2 端点清单、§3 示例) |
| 4 | UI 设计(页面结构、核心交互、组件/路由) | `ui-design.md` 全文(§2 路由、§3 组件树、§4 交互流程) |
| 5 | 与现有 staff-mgr 员工管理模块的衔接方案 | 本文 §6 + `database-design.md` §6 数据迁移 |
| 6 | 技术可行性说明与后续开发任务拆解 | `iteration1-task-breakdown.md` 全文 |
## 1. 概述
迭代1交付「Web 版人事系统(hr-web)」组织人事底座,功能范围 F01~F15:组织架构、职位职级体系、花名册、入职/转正/调动/离职/兼岗、合同台账(不含电子签)、流程审批、权限/日志、报表、工作台、员工服务与提醒。规模规格 100 人账号。
设计原则:
1. **全栈遵循 ocai 规范**:前端 bricks 组件体系(.ui 纯 JSON 声明式页面 + dspy 驱动的 CRUD),后端 ahserver(Python/aiohttp),数据层 apppublic/sqlor,基础模块 appbase(字典/用户)+ rbac(角色权限)。
2. **配置驱动**:表结构(models/*.json)、CRUD 界面(json/*.json)、字典(appcodes)全部声明式定义,减少硬编码,支撑 SRS 的"字段/流程/表单自定义"诉求。
3. **模块宿主无关**:每个模块仅依赖基础包与自己的数据表,通过 `load_{module}()` 注册 ServerEnv,可被任意宿主应用加载(module-development-spec)。
4. **单一事实来源**:花名册(roster_employee)是全系统员工数据唯一事实来源,入转调离审批通过后统一回写花名册。
## 2. 总体架构
```
┌────────────────────────────────────────────────────────────────────┐
│ 浏览器(PC Web 为主) │
│ bricks.js 渲染引擎:.ui(JSON) 页面 + DataViewer/Tree/Form/Chart │
└──────────────────────────────┬─────────────────────────────────────┘
│ HTTP(S) session cookie(Redis 会话)
┌──────────────────────────────▼─────────────────────────────────────┐
│ nginx 反向代理(prod: 443) │
└──────────────────────────────┬─────────────────────────────────────┘
┌──────────────────────────────▼─────────────────────────────────────┐
│ ahserver 应用进程(hr-web,dev:9080) │
│ 路由:wwwroot 自动路由 /{module}/{file}.ui|.dspy|.css|.js │
│ processors: .tmpl→tmpl, .ui→bui, .dspy→dspy │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 业务模块(load_xxx 注册 ServerEnv) │ │
│ │ hr-org | hr-roster | hr-flow | hr-contract | hr-report | │ │
│ │ hr-system │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ 基础模块:appbase(users/appcodes 字典)+ rbac(角色/权限) │ │
│ │ bricks_for_python(UiWindow 等 pybricks) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└──────────┬───────────────────────────────┬─────────────────────────┘
│ sqlor 连接池(sor.C/U/D/R/I/sqlExe) │ Redis session
┌──────────▼──────────┐ ┌────────▼────────┐
│ MySQL 8.0(库 hr) │ │ Redis(会话) │
│ json2ddl 生成 DDL │ └─────────────────┘
└─────────────────────┘ files/ 目录:附件存储(合同文件、头像、导入导出文件)
```
请求处理链路(以花名册列表为例):
1. 浏览器访问 `/hrroster/roster_list.ui`(由 json/roster_employee_list.json 经 xls2ui 生成的 DataViewer 页);
2. DataViewer 按 CRUD JSON 中的 `dataurl`/`new_data_url` 等调用 `/hrroster/api/xxx.dspy`;
3. ahserver 将 GET query + POST body 统一解析为 `params_kw` 注入 dspy 上下文;
4. dspy 调用 `load_hrroster()` 注册的业务函数(内部经 sqlor `sor.R` 查询,叠加 rbac 数据范围过滤与敏感字段脱敏);
5. dspy 显式 `return` JSON(列表必须 `{status:'success', data:{rows:[...], total:N}}`),bricks 渲染。
## 3. 技术选型与理由
| 层 | 选型 | 选型理由 |
|---|---|---|
| 前端 | bricks 组件体系 + .ui 纯 JSON 页面 | ocai 强制规范;CRUD JSON 自动生成列表/表单/树页面,F01~F15 中 80% 的增删改查页面零 Vue 代码;DataViewer 自带筛选(data_filter)、子表(subtables)、工具栏 bind |
| 声明式页面脚本 | .dspy(受控 Python) | 后端 API 与页面逻辑统一语言栈(Python);ahserver 自动解析参数、自动 JSON 序列化;禁止 import/print,天然受控 |
| 后端框架 | ahserver(aiohttp 异步) | ocai 强制规范;内置会话/RBAC/文件上传/后台任务;wwwroot 自动路由免手写路由注册;100 人规模单实例足够 |
| 数据访问 | apppublic/sqlor | 异步连接池 + 绑定参数防注入;`sor.C/U/D/R/I/sqlExe` 六元 API 覆盖全部数据操作;与 models JSON 配套 |
| 表定义 | models/*.json + json2ddl | database-table-definition-spec 标准;数据库无关的抽象类型,DDL 自动生成,保证 collate utf8mb4_unicode_ci 一致 |
| CRUD 生成 | json/*.json + xls2ui | crud-definition-spec 标准;自动产出列表页 .ui 与 add/update/delete dspy 包装 |
| 字典 | appbase appcodes/appcodes_kv | 性别/员工类型/异动类型/离职原因等枚举统一管理,CRUD 下拉直接引用(codes 段) |
| 权限 | rbac 模块 + 自研数据范围表 | rbac 管"功能权限(路径级)",hr-system 扩展"数据范围(组织维度/花名册字段维度)",满足 SRS §3.1.14 |
| 数据库 | MySQL 8.0 | 产线既有设施(staff-mgr 亦为 MySQL),sqlor DDL 模板成熟 |
| 会话 | Redis + aiohttp_session | web-application-spec 推荐;服务重启不丢登录 |
| 附件 | ahserver files/ 文件存储 | 合同附件、离职证明、导入导出文件;100 人规模本地目录足够 |
与 v1(Java/Spring Boot/JPA/JWT)口径的差异已全部废弃归档(`docs/_archive/`),不再保留双栈:鉴权由"自研 JWT Filter"改为"ahserver session + rbac",ORM 由 JPA 改为 sqlor,前端由"无"改为 bricks/dspy。
## 4. 模块划分与职责边界
### 4.1 迭代1模块清单(6 个,均挂载于 hr-web 应用)
| 模块 | 路由前缀 | 职责 | 数据表前缀 | 迭代1功能 |
|---|---|---|---|---|
| hr-org | /hrorg | 组织架构、职位职级体系、入转调离兼岗异动业务、黑名单、合同公司 | org_ | F01/F02/F04~F08 |
| hr-roster | /hrroster | 花名册(字段定义/字段值 EAV/员工主档/时间轴/工号规则)、导入导出 | roster_ | F03 |
| hr-flow | /hrflow | 表单定义、流程定义、流程实例、审批任务(待办/已办/我发起) | flow_ | F10 |
| hr-contract | /hrcontract | 合同台账、合同模板、到期提醒规则、合同审批 | contract_ | F09 |
| hr-report | /hrreport | 只读聚合报表(花名册分析/入职/转正/调岗/离职分析),无自有表 | — | F13 |
| hr-system | /hrsystem | 角色/管理员/数据范围、操作日志、工作台聚合、提醒规则、公告政策、站内消息 | sys_ | F11/F12/F14/F15 |
### 4.2 职责边界规则
1. **员工主数据唯一入口**:所有模块读写员工信息必须经 hr-roster 的注册函数(ServerEnv 暴露),禁止跨模块直接 SQL 写 roster_ 表。入转调离审批通过后由 hr-flow 回调 hr-org 的业务函数,再调用 hr-roster 回写接口更新员工状态。
2. **审批引擎与业务解耦**:hr-flow 只管"表单+流程+实例+任务"的流转,不含业务语义;业务回写通过 `biz_type + biz_id` 关联 + 流程完成回调(ServerEnv 注册的 hook 函数)实现。
3. **权限横切**:功能权限(路径级 RBAC)由 rbac 模块统一拦截(各模块 scripts/load_path.py 注册路径与角色);数据范围由 hr-system 提供 `get_data_scope(user_id)` 公共函数,hr-org/hr-roster/hr-report 在查询时强制叠加。
4. **操作日志横切**:所有写操作(sor.C/U/D)统一经 hr-system 的 `write_audit_log(...)` 记录前后值(before/after JSON),F12 查询页只读 sys_audit_log。
5. **字典统一**:任何枚举值不得硬编码在 dspy 中,一律走 appcodes(init/data.json 初始化),CRUD 下拉用 models codes 段引用。
### 4.3 hr-system 与 staff-mgr 的职责边界(重点澄清)
| 对象 | 性质 | 职责 | 状态 |
|---|---|---|---|
| **hr-system**(模块,repos/hr-system) | 迭代1 ocai 规范模块 | 系统支撑层:F11 权限管理、F12 操作日志、F14 工作台、F15 员工服务与提醒;并承载全应用的横切能力(数据范围、审计、站内消息) | 迭代1新建(复用现有空仓 hr-system.git) |
| **staff-mgr**(repos/staff-mgr) | v1 遗留 Java 模块 | 旧员工 CRUD REST 服务(/api/v1/staff/*,Spring Boot + JPA + JWT) | **已归档,冻结不再开发**;仅作迭代1数据迁移来源与参考实现(见 §6) |
边界结论:
- 迭代1上线后 staff-mgr **不再承担任何线上职责**;其员工数据一次性迁移至 hr-roster(roster_employee + roster_field_value),部门缓存迁移至 hr-org(org_unit),日志迁移/归档至 hr-system(sys_audit_log)。
- 旧模块的"员工 CRUD"职责由 **hr-roster** 承接(不是 hr-system);hr-system 只承接其"操作日志/权限"类横切职责。详见 §6 衔接方案。
- v1 应用定义 `apps/hr-system.md`(Java 后端服务)已废弃,与模块 hr-system(ocai 支撑模块)仅重名,无继承关系。
## 5. 模块结构与依赖关系
### 5.1 标准模块目录(module-development-spec)
每个业务模块仓库结构一致:
```
{module}/
├── {module}/ # Python 包
│ ├── __init__.py # 导出全部公共函数(三处注册同步点之一)
│ ├── init.py # load_{module}():ServerEnv 注册
│ └── *.py # 业务实现(service 层)
├── wwwroot/ # index.ui、menu、业务页 .ui、api/*.dspy、css/js
├── models/ # 表定义 JSON(json2ddl → mysql.ddl.sql)
├── json/ # CRUD 定义 JSON(xls2ui → 列表页 + add/update/delete dspy)
├── init/data.json # 字典与初始化数据(appcodes Format B)
├── scripts/load_path.py # RBAC 路径注册(角色:logined/admin/hr/manager/employee)
├── skill/SKILL.md # 模块 AI 说明(数据模型/端点/坑位)
├── pyproject.toml
└── README.md
```
### 5.2 依赖关系图
```
┌─────────── rbac(功能权限) ◄──── appbase(users/orgs/appcodes)
│ ▲ ▲
│ │ 路径注册/数据范围 │ 字典/用户
┌─────────┴──┐ ┌──────┴─────┐ ┌──────┴─────┐
│ hr-system │◄─────│ hr-flow │◄──────────│ hr-contract│
│ F11/12/14/15│ 审批回调/权限 │ 合同审批 │
└─────▲──────┘ └──────▲─────┘ └──────┬─────┘
│ 审计/数据范围 │ 入转调离审批 │ 员工/公司引用
│ │ │
┌─────┴──────┐ ┌──────┴─────┐ │
│ hr-report │─────►│ hr-org │◄─────────────────┘
│ (只读聚合) │ 异动数据 │ 组织/职位/异动 │
└─────▲──────┘ └──────┬─────┘
│ 花名册分析 │ 员工主数据读写(唯一入口)
│ ┌──────▼─────┐
└─────────────│ hr-roster │
│ 员工唯一事实源│
└────────────┘
依赖方向:箭头指向被依赖方。hr-report 只读,不被任何模块依赖。
```
依赖矩阵(行依赖列):
| ↓依赖→ | hr-org | hr-roster | hr-flow | hr-contract | hr-report | hr-system |
|---|---|---|---|---|---|---|
| hr-org | — | 回写花名册/黑名单 | 异动审批流转 | — | — | 权限/日志 |
| hr-roster | 组织/职位主数据 | — | 自助修改审核 | — | — | 字段级权限/脱敏/日志 |
| hr-flow | — | 表单引用字段/完成回调 | — | — | — | 审批角色权限/日志 |
| hr-contract | 合同公司主数据 | 员工关联 | 合同审批 | — | — | 提醒通道/日志 |
| hr-report | 异动数据 | 花名册数据 | — | — | — | 数据范围 |
| hr-system | 工作台引用组织 | 工作台引用档案 | 工作台引用待办 | 工作台引用合同 | — | — |
### 5.3 应用装配与部署
- 应用入口 `app/hr_web.py`:`init()` 中依次 `ServerEnv()` → 设置 `get_module_dbname`(返回 `hr`)→ `load_appbase()` → `load_rbac()` → `load_pybricks()` → `load_hrsystem/load_hrorg/load_hrroster/load_hrflow/load_hrcontract/load_hrreport()`。
- `conf/config.json`:processors 必含 `[".tmpl","tmpl"], [".ui","bui"], [".dspy","dspy"]`;indexes 含 `index.ui`;databases 配置 hr 库;Redis session(session_max_time/session_issue_time)。
- build.sh 顺序:建 venv → pip install apppublic/sqlor/ahserver/bricks/appbase/rbac → clone 6 个业务模块 pip install → 各模块 models/ 执行 `json2ddl mysql . > mysql.ddl.sql` 并导入 → init/data.json 导入 → json/ 执行 `xls2ui` 生成 CRUD → symlink 各模块 wwwroot 与 bricks dist → systemd 服务。
- 环境:dev/test 单实例容器(nginx:8080 → ahserver:9080);prod nginx:443 反代,ahserver 不直接暴露,MySQL/Redis 仅内网;数据每日备份。100 人规模单实例即可满足"列表页 ≤500ms"指标(见 §8)。
## 6. 与现有 staff-mgr 员工管理模块的衔接方案
### 6.1 衔接定位
staff-mgr 是 v1 Java 口径下的员工 CRUD 模块(已通过端到端验证,见其 README),与 ocai 规范冲突,不改造、不并行演进,采取**一次性数据迁移 + 接口替代 + 退役**策略。
### 6.2 职责与接口替代映射
| staff-mgr 旧接口(/api/v1/staff) | 迭代1替代(hr-roster/hr-system dspy) | 说明 |
|---|---|---|
| POST /(创建员工) | /hrroster/api/roster_employee_create.dspy(或手动入职 /hrorg/api/entry_manual.dspy) | 新建一律走入职流程或花名册新增 |
| GET /(分页列表) | /hrroster/roster_list(CRUD 列表 + data_filter) | 筛选能力增强(自定义字段) |
| GET /{id}(详情) | /hrroster/employee_detail.dspy | 档案 Tabs + 时间轴 |
| PUT /{id}(更新) | /hrroster/api/roster_employee_update.dspy | 可配置自助修改 + 审核 |
| DELETE /{id}、POST /batch-delete | 离职流程 /hrorg/api/leave_manual.dspy | 迭代1不做物理/逻辑删除员工,统一走离职 |
| GET /{id}/change-logs | /hrroster/timeline.dspy | 时间轴(含迁移进来的旧变更记录) |
| GET /audit-logs | /hrsystem/audit_log_list(CRUD) | 历史日志迁入 sys_audit_log |
| GET /check/employee-no | /hrroster/api/check_employee_no.dspy | 工号查重保留 |
| GET /departments | /hrorg/api/org_tree.dspy | 部门缓存表废弃,直接查 org_unit |
脱敏规则延续并按 SRS 加强:身份证号全角色脱敏(前3后3)、手机号(前3后4)、薪资职级类字段仅 admin/hr 可见——由 hr-system 字段级权限表驱动,替代 staff-mgr 硬编码 MaskUtil。
### 6.3 数据迁移方案(详见 database-design.md §6)
- 迁移对象:staff_employee → roster_employee;staff_employee_extra → roster_field_value(学历/专业等映射到预置字段定义)+ contract_info(合同起止);staff_department_cache → org_unit;staff_change_log → roster_timeline;staff_audit_log → sys_audit_log。
- 迁移工具:hr-system 仓内 `scripts/migrate_staff_mgr.py`(Python + sqlor,幂等、按 employee_no 去重、输出核对报告)。
- 切换策略:迭代1 UAT 通过后一次性切换;切换后 staff-mgr 服务下线、仓库冻结归档(保留只读访问一个迭代周期)。
### 6.4 风险与对策
| 风险 | 对策 |
|---|---|
| 旧数据字段语义不一致(如 status 枚举) | 迁移脚本内置映射表 + 人工核对报告;UAT 期间双库比对 |
| 旧 department_id 无组织主数据 | 先迁 staff_department_cache 建 org_unit,再按 department_id 关联 |
| 身份证号加密算法不同(AesCipher) | 迁移时解密后按 hr-web 统一加密方案重新落库 |
## 7. 安全设计
1. **鉴权**:ahserver session(Redis)+ rbac 角色。迭代1角色:`admin`(系统管理员)、`hr`(人事)、`manager`(部门经理)、`employee`(员工自助);所有路径经 scripts/load_path.py 注册,未注册路径默认拒绝。
2. **数据范围**(F11):sys_role_data_scope 支持两类范围——组织维度(org_id 集合,含子树)、花名册字段维度(field_id 可见集合)。查询层强制拼接范围条件;报表同样受控。
3. **脱敏**:敏感字段(身份证/手机/薪资级)在 roster_field_def 标记 sensitive,dspy 返回前按角色脱敏。
4. **审计**(F12):全量写操作记录前后值 JSON、操作人、IP;审批、导入导出同样记录。
5. **SQL 注入**:全部经 sqlor 绑定参数;dspy 禁止拼接用户输入(sqlExe 使用 `${var}$` 占位)。
## 8. 非功能设计响应
| SRS 非功能项 | 设计响应 |
|---|---|
| 列表响应 ≤500ms | 100 人规模 + 索引设计(database-design.md §5);列表页分页默认 50;组织树一次性加载(≤200 节点) |
| 批量导入导出 100 人量级 | openpyxl 同步处理(ahserver 已内置),导入走后台任务 + 结果文件下载 |
| 字段/流程/表单自定义 | roster_field_def(EAV)、flow_form_def/flow_def JSON 化配置,无硬编码 |
| PC 为主 + 员工自助移动端兼容 | 员工自助页面(个人档案/发起审批/待办)使用 ResponsableBox 自适应布局 |
## 9. 配套设计文档
- `database-design.md`(v2.0):ER、43 张表结构、索引、字典、models JSON 示例、staff-mgr 迁移策略
- `api-design.md`(v2.0):dspy 端点清单(约 100 个)、请求/响应、鉴权角色、错误码
- `ui-design.md`(v2.0):页面结构、组件树、路由表、核心交互流程
- `iteration1-task-breakdown.md`(v1.0):可行性分析、风险、开发任务拆解(T01~T24)