# 迭代1总体设计 —— API/接口设计(hr-web,ocai 口径) - 版本:v2.0(迭代1-总体设计,取代 v1 Spring Boot/JWT RESTful 口径,v1 已归档至 `docs/_archive/01-design-v1-old/api-design.md`) - 状态:提交审核 - 规范依据:ocai 技能集 module-development-spec、dspy-file-implementation-spec、crud-definition-spec;架构基线 `docs/01-design/architecture.md` ## 1. 接口范式与通用约定(取代 v1 RESTful/JWT) 1. **端点形态**:全部接口为 `.dspy` 端点(受控 Python),路径 `/{module}/api/{name}.dspy`,由 ahserver wwwroot 自动路由,无需注册;GET query 与 POST body 统一解析进 `params_kw`。dspy 必须显式 `return`,且不得在 `async with db.sqlorContext` 块内 return(NoneType 陷阱)。 2. **鉴权**:session cookie(Redis 会话)+ rbac 路径级角色控制;每条路径经各模块 `scripts/load_path.py` 注册,角色:`admin / hr / manager / employee / logined`;未注册路径默认拒绝。不使用 JWT。 3. **数据范围**:查询类接口强制叠加 `get_data_scope(user_id)`(hr-system 提供),范围外数据不可见。 4. **CRUD 列表/表单接口**:由 json/*.json 经 xls2ui 自动生成(列表查询、add/update/delete 包装 dspy),不在本文逐一列出;本文列出业务自定义端点。CRUD 自动生成部分约定 `new_data_url/update_data_url/delete_data_url` 位于 params 顶层。 5. **统一响应结构**: - 列表:`{"status":"success","data":{"rows":[...],"total":N}}`(DataViewer 强制格式) - 业务:`{"status":"success"|"error","message":"...","data":{...}}` - 错误返回 HTTP 200 + status=error(bricks 前端惯例),鉴权失败由框架返回 401/403。 6. **错误码规范**(status=error 时 data.code): | code | 含义 | 典型场景 | |---|---|---| | PARAM_INVALID | 参数缺失/格式错误 | 必填项为空、日期格式错 | | NOT_FOUND | 对象不存在 | id 无效 | | DUPLICATE | 唯一冲突 | 工号/编码重复 | | NO_PERMISSION | 数据范围外/角色不足 | 越权访问他人档案 | | BLACKLIST_HIT | 黑名单命中 | 入职校验拦截 | | STATUS_CONFLICT | 状态不允许 | 已离职再调动、重复审批 | | FLOW_NOT_MATCH | 无匹配审批流 | 该员工类型/部门未配置流程 | | SCOPE_OVERDRAFT | 编制/范围超限 | 预留(编制管理范围外) | | INTERNAL_ERROR | 服务异常 | 兜底 | 7. **审计**:所有写接口成功后调用 `write_audit_log(module,target_type,target_id,op,before,after)`(hr-system)。 ## 2. 端点清单(按模块) > 角色列:A=admin,H=hr,M=manager,E=employee,L=任意登录。数据范围=是 表示叠加数据范围过滤。 > CRUD 列:该表的标准增删改查由 json/*.json 自动生成,路径为 `/{module}/{tblname}_list`(页面) + `add_/update_/delete_{tblname}.dspy`。 ### 2.1 hr-org(/hrorg,F01/F02/F04~F08) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | CRUD | |---|---|---|---|---|---|---| | /hrorg/api/org_tree.dspy | GET | 组织树(支持 as_of 历史日期、status 过滤) | as_of?, status?, keyword? | 树形节点数组(id,name,parent_id,children,status,leader_name) | L | — | | /hrorg/api/org_unit_create.dspy | POST | 新建组织(编码唯一校验、写 org_unit_change) | org_code,org_name,parent_id,org_type,leader_id,effective_date,自定义字段kv | {id} | A,H | org_unit | | /hrorg/api/org_unit_update.dspy | POST | 编辑组织(留痕) | id,... | {} | A,H | org_unit | | /hrorg/api/org_unit_move.dspy | POST | 移动组织(变更上级) | id,new_parent_id,effective_date | {} | A,H | — | | /hrorg/api/org_unit_disable.dspy | POST | 停用组织(校验无在职员工引用) | id | {} | A,H | — | | /hrorg/api/org_change_timeline.dspy | GET | 组织时间轴 | org_id | {rows:[{change_type,before_json,after_json,operator,created_at}]} | L | — | | /hrorg/api/org_import.dspy | POST | Excel 批量导入组织(后台任务+结果文件) | file(上传) | {task_id} | A,H | — | | /hrorg/api/org_import_result.dspy | GET | 导入结果(成功/失败行下载) | task_id | {rows,total,error_file} | A,H | — | | /hrorg/api/org_chart_export.dspy | GET | 架构图导出(图片/XMind) | as_of?, root_id?, fmt=png/xmind | 文件下载 | A,H | — | | /hrorg/api/org_field_def_save.dspy | POST | 组织字段自定义 | fields[] | {} | A,H | org_field_def | | /hrorg/api/job_import.dspy / job_export.dspy | POST/GET | 职务批量导入导出 | file / 筛选条件 | {task_id} / 文件 | A,H | org_job | | /hrorg/api/position_import.dspy / position_export.dspy | POST/GET | 职位批量导入导出 | file / dept_id?,status? | {task_id} / 文件 | A,H | org_position | | /hrorg/api/job_options.dspy / position_options.dspy / grade_options.dspy / rank_options.dspy / sequence_options.dspy / org_options.dspy / company_options.dspy | GET | 主数据下拉选项(状态=active;供花名册/异动表单引用) | 无/keyword | {rows:[{id,name}]} | L | org_job 等 | | /hrorg/api/entry_approval.dspy | POST | 发起入职审批(单个,匹配审批流) | name,id_number,employee_type,org_id,position_id,hire_date,... | {instance_id} 或 BLACKLIST_HIT/FLOW_NOT_MATCH | A,H | org_entry | | /hrorg/api/entry_manual.dspy | POST | 手动入职(批量,直接入花名册或待入职) | entries[]:{...}, to=pending/roster | {entered:[],pending:[],blocked:[]} | A,H | — | | /hrorg/api/entry_invite_register.dspy | POST | 邀请待入职填写登记表(站内消息) | entry_ids[] | {} | A,H | — | | /hrorg/api/entry_register_submit.dspy | POST | 待入职员工提交登记表 | entry_id,字段kv | {} | E(本人) | — | | /hrorg/api/entry_blacklist_check.dspy | GET | 黑名单校验 | id_number 或 name | {hit:bool,blacklist_id?} | A,H | — | | /hrorg/api/entry_notify.dspy | POST | 入职通知/欢迎(模板填充发送) | entry_ids[],template_id | {} | A,H | — | | /hrorg/api/regular_apply.dspy | POST | 发起转正审批(自助/代发) | employee_id,regular_date | {instance_id} | E(自助),H,M | org_regularization | | /hrorg/api/regular_manual.dspy | POST | 手动转正(直接更新花名册) | employee_id,regular_date | {} | A,H | — | | /hrorg/api/transfer_apply.dspy | POST | 发起调动审批(支持批量人员) | transfer_type,effective_date,details[]:{employee_id,to_org_id,to_position_id,...} | {instance_id} | A,H,M | org_transfer | | /hrorg/api/transfer_cancel.dspy | POST | 取消调动(保留历史不改现职) | transfer_id | {} | A,H | — | | /hrorg/api/leave_apply.dspy | POST | 发起离职审批(自助/代发) | employee_id,leave_type,leave_reason,leave_date | {instance_id} | E(自助),H,M | org_leave | | /hrorg/api/leave_manual.dspy | POST | 手动离职(直接入待离职/已离职) | employee_id,leave_date,leave_reason | {} | A,H | — | | /hrorg/api/leave_handover_save.dspy | POST | 离职交接项保存 | leave_id,items[] | {} | A,H,M | org_leave_handover | | /hrorg/api/leave_certificate.dspy | GET | 离职证明生成下载 | employee_id | 文件 | E(本人),A,H | — | | /hrorg/api/blacklist_add.dspy | POST | 一键加入黑名单 | leave_id 或 id_number_hash,reason | {} | A,H | org_blacklist | | /hrorg/api/concurrent_apply.dspy | POST | 发起兼岗审批 | employee_id,org_id,position_id,start_date,end_date | {instance_id} | A,H,M | org_concurrent_post | | /hrorg/api/workbench_transfer.dspy | GET | 工作台入转调离汇总(经理/管理员视图) | scope=team/all | {entry_n,regular_n,transfer_n,leave_n,rows} | M,H,A | — | ### 2.2 hr-roster(/hrroster,F03) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | CRUD | |---|---|---|---|---|---|---| | /hrroster/api/roster_list.dspy | GET | 花名册列表(主表条件分页 + EAV 拼装 + 范围过滤 + 脱敏) | keyword?,org_id?,status?,type?,field_filters?,page,size | {rows,total} | A,H(范围),M(团队) | roster_employee | | /hrroster/api/roster_employee_create.dspy | POST | 新增员工(自动生成工号、写时间轴) | 主档字段+自定义字段kv | {id,employee_no} | A,H | roster_employee | | /hrroster/api/roster_employee_update.dspy | POST | 更新员工(含自助修改路径:可改范围+可选审核) | id,字段kv | {} 或 {need_audit:true,instance_id} | A,H,E(本人受限) | roster_employee | | /hrroster/api/employee_detail.dspy | GET | 员工档案详情(主档+分组字段+兼岗+合同摘要,按角色脱敏/字段可见) | id | {main,groups:[{group_name,fields:[...]}],concurrent,contracts} | L(按范围) | — | | /hrroster/api/roster_field_def_save.dspy | POST | 字段定义维护(类型/必填/敏感/自助/审核/适用类型/排序) | fields[] | {} | A,H | roster_field_def | | /hrroster/api/roster_field_group_save.dspy | POST | 分组维护(含拖拽排序持久化) | groups[] | {} | A,H | roster_field_group | | /hrroster/api/roster_type_rule_save.dspy | POST | 员工类型字段规则 | employee_type,field_rules_json | {} | A,H | roster_type_rule | | /hrroster/api/empno_rule_save.dspy | POST | 工号规则维护 | rules[] | {} | A,H | roster_empno_rule | | /hrroster/api/check_employee_no.dspy | GET | 工号查重 | employee_no,exclude_id? | {exists} | A,H | — | | /hrroster/api/roster_import.dspy | POST | 自定义导入(批量新增/批量修改,模板按当前字段定义生成) | file,mode=new/update | {task_id} | A,H | — | | /hrroster/api/roster_import_template.dspy | GET | 导入模板下载(按字段定义+类型规则) | employee_type? | 文件 | A,H | — | | /hrroster/api/roster_export.dspy | GET | 自定义导出(指定数据日期/字段/顺序) | as_of?,field_ids[],filters | {task_id}→文件 | A,H | — | | /hrroster/api/timeline.dspy | GET | 员工时间轴(全周期记录) | employee_id,event_type? | {rows:[{event_type,event_date,title,content_json}]} | L(按范围) | — | | /hrroster/api/timeline_add.dspy | POST | 时间轴记录编辑 | employee_id,event_type,event_date,title,content | {} | A,H | — | | /hrroster/api/file_preview_url.dspy | GET | 附件在线预览 URL(files/ 受控访问) | file_id | {url} | L(按范围) | — | | /hrroster/api/roster_writeback.dspy | 内部 | 异动回写入口(仅 hr-org/hr-flow 服务内调用,注册于 ServerEnv,不注册 HTTP 路径) | biz_type,biz_id,instance_id | {} | 服务内 | — | ### 2.3 hr-flow(/hrflow,F10) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | CRUD | |---|---|---|---|---|---|---| | /hrflow/api/form_def_save.dspy | POST | 表单定义维护(引用花名册字段) | form_code,form_name,biz_type,fields_json | {id} | A,H | flow_form_def | | /hrflow/api/flow_def_save.dspy | POST | 流程定义(匹配条件、版本) | flow_code,form_id,biz_type,match_cond_json | {id} | A,H | flow_def | | /hrflow/api/flow_node_save.dspy | POST | 节点维护(审批人/通过规则/字段权限/抄送) | flow_id,nodes[] | {} | A,H | flow_node_def | | /hrflow/api/flow_role_save.dspy | POST | 审批角色(流程范围/操作权限/数据范围) | role_name,flow_scope_json,op_perm_json,data_scope_json | {id} | A | flow_role | | /hrflow/api/flow_match.dspy | GET | 按员工类型/部门匹配流程 | biz_type,employee_type,org_id | {flow_id,flow_name} 或 FLOW_NOT_MATCH | 服务内/HR | — | | /hrflow/api/flow_start.dspy | POST | 发起审批(通用:业务模块统一入口) | biz_type,biz_id,form_data_json | {instance_id,inst_no} | L | — | | /hrflow/api/task_todo.dspy | GET | 我的待办 | page,size | {rows,total} | L | — | | /hrflow/api/task_done.dspy | GET | 已办 | page,size | {rows,total} | L | — | | /hrflow/api/inst_mine.dspy | GET | 我发起的 | status?,page,size | {rows,total} | L | — | | /hrflow/api/inst_received.dspy | GET | 我收到的(抄送/知会) | page,size | {rows,total} | L | — | | /hrflow/api/inst_detail.dspy | GET | 审批单详情(表单数据+流转记录+字段权限) | instance_id | {form_data,nodes:[{op,operator,time,comment}],status} | L(参与者) | — | | /hrflow/api/task_approve.dspy | POST | 同意 | task_id,comment? | {} | L(任务归属) | — | | /hrflow/api/task_reject.dspy | POST | 驳回 | task_id,comment | {} | L(任务归属) | — | | /hrflow/api/task_forward.dspy | POST | 转交 | task_id,to_user_id | {} | L(任务归属) | — | | /hrflow/api/inst_withdraw.dspy | POST | 撤回(发起人,仅首节点未处理) | instance_id | {} | L(发起人) | — | | /hrflow/api/inst_query.dspy | GET | 审批数据查询(表单/状态/发起人/编号/时间) | form_name?,status?,initiator?,inst_no?,date_range | {rows,total} | A,H | — | | /hrflow/api/inst_export.dspy | GET | 审批数据导出 | 同上 | 文件 | A,H | — | | /hrflow/api/inst_print.dspy | GET | 审批单打印视图 | instance_id | 打印 HTML | L(参与者) | — | ### 2.4 hr-contract(/hrcontract,F09) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | CRUD | |---|---|---|---|---|---|---| | /hrcontract/api/contract_list.dspy | GET | 合同台账列表(范围过滤) | employee_id?,status?,type_id?,end_before? | {rows,total} | A,H | contract_info | | /hrcontract/api/contract_create.dspy | POST | 新增合同(模板引用) | employee_id,type_id,company_id,start_date,end_date,file | {id} | A,H | contract_info | | /hrcontract/api/contract_import.dspy | POST | 批量导入 | file | {task_id} | A,H | — | | /hrcontract/api/contract_apply.dspy | POST | 合同审批(新签/续签/变更/终止) | contract_id?,biz_type=contract_sign/renew/change/stop,form_data_json | {instance_id} | A,H | — | | /hrcontract/api/contract_remind_rule_save.dspy | POST | 到期提醒规则 | rule_name,days_before,target_role,content_template | {id} | A,H | contract_remind_rule | | /hrcontract/api/contract_template_save.dspy | POST | 模板维护 | template_name,type_id,file,field_marks_json | {id} | A,H | contract_template | | /hrcontract/api/contract_expire_soon.dspy | GET | 即将到期列表(工作台/提醒) | days | {rows} | A,H | — | | /hrcontract/api/esign_stub.dspy | POST | 电子签预留桩(返回未启用提示) | contract_id | {status:'error',code:'NOT_ENABLED'} | A,H | — | ### 2.5 hr-report(/hrreport,F13,全部只读) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | |---|---|---|---|---|---| | /hrreport/api/roster_analysis.dspy | GET | 花名册多维分布(维度可自定义) | dim=type/status/age/edu/location/gender/custom_field_id | {rows:[{dim_value,count}],total} | A,H,M(范围) | | /hrreport/api/entry_analysis.dspy | GET | 入职数量与趋势 | dim=org/location/position,period=month/quarter | {trend:[],rows:[]} | A,H,M | | /hrreport/api/regular_analysis.dspy | GET | 转正分析与近期待转正 | period? | {trend:[],upcoming:[]} | A,H,M | | /hrreport/api/transfer_analysis.dspy | GET | 近期调动分析 | date_range? | {rows:[{type,count}]} | A,H,M | | /hrreport/api/leave_analysis.dspy | GET | 离职分析(待离职数/原因/离职率/同环比) | period? | {pending_n,reasons:[],rate:{cur,yoy,mom}} | A,H,M | | /hrreport/api/team_stats.dspy | GET | 团队人事统计(经理工作台卡片) | org_id? | {headcount,entry_n,leave_n,...} | M,H,A | ### 2.6 hr-system(/hrsystem,F11/F12/F14/F15) | 端点 | 方法 | 功能 | 关键请求参数 | 响应 data | 角色 | CRUD | |---|---|---|---|---|---|---| | /hrsystem/api/role_save.dspy | POST | 管理角色创建与权限设置(rbac 角色+路径) | role_name,paths[] | {id} | A | sys_role(rbac) | | /hrsystem/api/data_scope_save.dspy | POST | 人员范围(组织维度/花名册字段维度) | admin_user_id,scope_type,org_ids[]/field_ids[] | {} | A | sys_data_scope | | /hrsystem/api/admin_save.dspy | POST | 管理员管理(创建/调整权限) | user_id,roles[] | {} | A | — | | /hrsystem/api/get_data_scope.dspy | 内部 | 数据范围查询(ServerEnv 注册,供各模块查询叠加) | user_id | {scope_type,org_ids,field_ids} | 服务内 | — | | /hrsystem/api/audit_log_list.dspy | GET | 操作日志查询(时间/类型/模块) | module?,operation?,date_range,target? | {rows,total} | A | sys_audit_log | | /hrsystem/api/audit_log_detail.dspy | GET | 日志详情(前后对比) | id | {before_json,after_json,operator,ip,time} | A | — | | /hrsystem/api/workbench_employee.dspy | GET | 员工工作台聚合(档案摘要/待办数/可发起流程) | — | {profile,todo_n,flows:[...]} | E | — | | /hrsystem/api/workbench_manager.dspy | GET | 经理工作台聚合(团队统计/待审批/关怀提醒) | — | {stats,todos:[],care:[...]} | M | — | | /hrsystem/api/workbench_admin.dspy | GET | 管理员工作台聚合(人事统计/合同到期/提醒) | — | {stats,contracts:[],care:[...]} | A,H | — | | /hrsystem/api/remind_rule_save.dspy | POST | 通用提醒规则(多场景自定义) | scene_code,rule_name,days_before,content_template,target_role | {id} | A,H | sys_remind_rule | | /hrsystem/api/care_config_save.dspy | POST | 生日/周年关怀文案配置 | care_type,template,enable | {} | A,H | sys_care_config | | /hrsystem/api/announcement_save.dspy | POST | 公告发布 | title,content,publish_scope_json | {id} | A,H | sys_announcement | | /hrsystem/api/policy_save.dspy | POST | 政策发布(查阅范围/下载权限) | title,file,view_scope_json,downloadable | {id} | A,H | sys_policy | | /hrsystem/api/message_list.dspy | GET | 我的站内消息 | is_read?,msg_type? | {rows,total} | L | — | | /hrsystem/api/message_read.dspy | POST | 标记已读 | ids[] | {} | L | — | | /hrsystem/api/remind_scan.dspy | 内部 | 提醒扫描(cron/启动任务:转正/合同/生日等到期扫描推送) | — | {sent_n} | 服务内 | — | ### 2.7 appbase/rbac 沿用(不自研) 登录/登出、用户管理、字典 appcodes 维护、rbac 角色路径绑定由 appbase/rbac 包自带页面与端点承载(`/appbase/*`、`/rbac/*`),迭代1仅在各模块 init/data.json 中初始化字典与角色。 ## 3. 典型调用链示例 ### 3.1 转正审批(F05 流程2) 1. `POST /hrorg/api/regular_apply.dspy` {employee_id, regular_date} → hr-org 校验员工状态(probation); 2. 内部调用 `flow_match(biz_type=regular, employee_type, org_id)` → `POST /hrflow/api/flow_start.dspy` {biz_type:'regular', biz_id:regularization_id, form_data_json} → 生成 flow_instance + 首节点 flow_task,发站内消息给审批人; 3. 审批人 `POST /hrflow/api/task_approve.dspy` → 全部节点通过 → flow_instance.inst_status=approved; 4. hr-flow 完成回调 → hr-org `regular_confirm` → hr-roster `roster_writeback`:roster_employee.employee_status=regular、regular_date 更新,写 roster_timeline; 5. 各步均写 sys_audit_log;转正提醒由 `remind_scan` 按预计日期提前推送管理员与直属主管。 ### 3.2 花名册列表(F03) `GET /hrroster/api/roster_list.dspy?keyword=张&org_id=..&status=regular&page=1&size=50` → 权限校验(数据范围 org_ids/field_ids)→ 主表条件分页 → 值表批量拼装显示字段 → 敏感字段按角色脱敏 → `{"status":"success","data":{"rows":[...],"total":N}}`。 ### 3.3 入职黑名单拦截(F04) `POST /hrorg/api/entry_approval.dspy` → id_number 哈希匹配 org_blacklist → 命中返回 `{"status":"error","data":{"code":"BLACKLIST_HIT"}}`,前端待入职列表高亮标注;手动入职返回 blocked 名单。 ## 4. 与 v1 接口的替代关系(衔接 staff-mgr) | v1 端点(JWT RESTful) | 迭代1替代 | |---|---| | POST /api/v1/staff | /hrorg/api/entry_manual.dspy 或 /hrroster/api/roster_employee_create.dspy | | GET /api/v1/staff | /hrroster/api/roster_list.dspy(+ CRUD 列表页) | | GET /api/v1/staff/{id} | /hrroster/api/employee_detail.dspy | | PUT /api/v1/staff/{id} | /hrroster/api/roster_employee_update.dspy | | DELETE /api/v1/staff/{id}、batch-delete | 离职流程 /hrorg/api/leave_manual.dspy(迭代1不做删除语义,裁决 Q6) | | GET /api/v1/staff/{id}/change-logs | /hrroster/api/timeline.dspy | | GET /api/v1/staff/audit-logs | /hrsystem/api/audit_log_list.dspy | | GET /api/v1/staff/check/employee-no | /hrroster/api/check_employee_no.dspy | | GET /api/v1/staff/departments | /hrorg/api/org_tree.dspy | ## 5. 安全设计要点 1. 所有路径 load_path.py 注册;未注册即拒绝(含 .css/.js 静态资源)。 2. 敏感字段(身份证全角色脱敏前3后3;手机前3后4;职级/薪资类字段仅 admin/hr)由 roster_field_def.sensitive + 角色矩阵驱动,服务端脱敏后返回。 3. 全部 SQL 经 sqlor 绑定参数(`${var}$` 占位),禁止拼接用户输入。 4. 审批操作校验任务归属人;员工自助类接口校验本人(或白名单字段)。 5. 导入导出、登录、权限变更全量写 sys_audit_log(含 IP)。