242 lines
22 KiB
Markdown
242 lines
22 KiB
Markdown
# 需求文档:租户管理后台(tenant-admin-web)
|
||
|
||
## 简介
|
||
|
||
构建独立的租户管理 Web 应用(`apps/tenant-admin/`),面向租户管理员,提供用户审核与管理、Excel 数据上传(4 种模板)、维客线索管理三大功能模块。前端采用 React + Vite + Ant Design(与 `apps/admin-web/` 同技术栈),后端复用 `apps/backend/` FastAPI 新增 4 个路由模块。认证体系与小程序完全隔离,使用独立的 `auth.tenant_admins` 表、用户名+密码登录、不同 JWT audience。所有数据查询附加 `site_id IN (管辖列表)` 条件实现多门店数据隔离。
|
||
|
||
## 术语表
|
||
|
||
- **Tenant_Admin_Web**:租户管理后台前端应用,部署在 `apps/tenant-admin/`
|
||
- **Backend_API**:FastAPI 后端服务,部署在 `apps/backend/`,为 Tenant_Admin_Web 提供 RESTful API
|
||
- **Tenant_Admin**:租户管理员,由系统管理后台 Operator 创建的账号,使用用户名+密码登录租户管理后台
|
||
- **Operator**:系统管理后台操作员,在 `apps/admin-web/` 中管理租户管理员账号
|
||
- **Site**:门店,通过 `site_id` 标识,是多门店数据隔离的基本单位
|
||
- **Site_Code**:球房编号,用户申请时输入的门店标识码,通过 `auth.site_code_mapping` 映射到 `site_id`
|
||
- **User_Application**:用户入驻申请,小程序用户提交的申请记录,存储在 `auth.user_applications`
|
||
- **Retention_Clue**:维客线索,助教为会员记录的销售/维护线索,存储在 `public.member_retention_clue`
|
||
- **Staging_Table**:暂存表,Excel 上传数据先写入业务库暂存表,再由 ETL 同步到 DWS 层
|
||
- **Upload_Batch**:上传批次,一次 Excel 上传操作的记录,存储在 `biz.excel_upload_log`
|
||
- **Salary_Adjustment**:助教奖罚明细,通过 Excel 上传写入 `biz.salary_adjustments`
|
||
- **Managed_Site_Ids**:管辖门店列表,Tenant_Admin 被授权管理的 `site_id` 数组
|
||
|
||
## 需求
|
||
|
||
### 需求 1:租户管理员认证
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望通过用户名和密码登录租户管理后台,以便安全地执行用户审核、数据上传等管理操作。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 提交有效的用户名和密码, THE Backend_API SHALL 验证凭据(bcrypt 哈希比对),签发 JWT 访问令牌(audience 为 `tenant-admin`,与小程序 `xcx` 隔离),并返回访问令牌和刷新令牌
|
||
2. WHEN Tenant_Admin 提交无效的用户名或密码, THE Backend_API SHALL 返回 401 状态码和错误描述,不泄露具体是用户名还是密码错误
|
||
3. IF Tenant_Admin 账号状态为禁用(`is_active = false`), THEN THE Backend_API SHALL 返回 403 状态码并提示账号已被禁用
|
||
4. WHILE Tenant_Admin 持有有效的 JWT 令牌, THE Backend_API SHALL 允许访问 `/api/tenant/*` 路径下的受保护端点
|
||
5. WHEN JWT 访问令牌过期, THE Tenant_Admin_Web SHALL 使用刷新令牌自动获取新的访问令牌;WHEN 刷新令牌也过期, THE Tenant_Admin_Web SHALL 将 Tenant_Admin 重定向到登录页面
|
||
6. THE Backend_API SHALL 通过 JWT 中的 `aud` 字段区分租户管理员和小程序用户,使用独立的认证依赖注入(`require_tenant_admin()`),拒绝小程序 JWT 访问租户管理端点
|
||
|
||
### 需求 2:数据隔离与权限控制
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望只能查看和操作自己管辖门店的数据,以确保多门店之间的数据安全隔离。
|
||
|
||
#### 验收标准
|
||
|
||
1. THE Backend_API SHALL 在所有租户管理端点的数据查询中附加 `site_id IN (managed_site_ids)` 条件,其中 `managed_site_ids` 从当前 Tenant_Admin 的 JWT 令牌或 `auth.tenant_admins` 记录中获取
|
||
2. WHEN Tenant_Admin 的 `managed_site_ids` 包含多个 site_id, THE Backend_API SHALL 支持跨门店聚合查询(合并多个 site_id 的结果)
|
||
3. IF Tenant_Admin 尝试访问不在其 `managed_site_ids` 范围内的数据, THEN THE Backend_API SHALL 返回 403 状态码
|
||
4. THE Tenant_Admin_Web SHALL 在页面顶部提供门店筛选器,允许 Tenant_Admin 在管辖范围内切换或选择门店
|
||
|
||
### 需求 3:用户申请审核
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望查看和审核小程序用户的入驻申请,以便控制哪些用户可以使用系统。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 打开用户审核页面, THE Tenant_Admin_Web SHALL 从 Backend_API 获取管辖门店范围内的用户申请列表,支持按状态筛选(全部 / 待审核 pending / 已通过 approved / 已拒绝 rejected),支持分页
|
||
2. THE Backend_API SHALL 返回每条申请的以下信息:申请人昵称、手机号、球房编号(site_code)、申请角色文本、员工编号、申请时间、当前状态
|
||
3. WHEN Tenant_Admin 查看某条待审核申请的关联建议, THE Backend_API SHALL 根据申请中的球房编号通过 `auth.site_code_mapping` 查得 `site_id`,再并行匹配 `fdw_etl.v_dim_assistant`(phone 匹配,`scd2_is_current=1`)和 `fdw_etl.v_dim_staff` + `v_dim_staff_ex`(phone 匹配),返回匹配建议列表
|
||
4. WHEN Tenant_Admin 审核通过一条申请, THE Backend_API SHALL 接受角色(助教/管理者/员工)、关联助教 ID(可选)、关联员工 ID(可选),执行以下操作:更新 `auth.users.status = 'approved'`、写入 `auth.user_site_roles`(分配角色)、写入 `auth.user_assistant_binding`(关联助教/员工,含 staff_id)、更新 `auth.user_applications.status = 'approved'` 及审核人和审核时间
|
||
5. WHEN Tenant_Admin 审核拒绝一条申请, THE Backend_API SHALL 接受拒绝原因(必填),更新 `auth.user_applications.status = 'rejected'`、`review_note` 和审核时间
|
||
6. IF 申请状态不是 pending, THEN THE Backend_API SHALL 拒绝审核操作并返回 409 状态码
|
||
|
||
### 需求 4:用户管理
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望管理已通过审核的用户信息,包括修改角色、店铺归属和助教绑定关系,以便维护用户数据的准确性。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 打开用户管理页面, THE Tenant_Admin_Web SHALL 展示管辖门店范围内已通过审核的用户列表,包含姓名、角色、关联助教姓名、所属门店、账号状态,支持按角色筛选和关键词搜索,支持分页
|
||
2. WHEN Tenant_Admin 编辑用户信息, THE Backend_API SHALL 允许修改以下字段:角色(助教/管理者/员工)、所属门店(`site_id`,限管辖范围内)、账号状态(启用/禁用)
|
||
3. WHEN Tenant_Admin 修改用户的助教/员工绑定关系, THE Backend_API SHALL 更新 `auth.user_assistant_binding` 记录,接受新的 `assistant_id` 和/或 `staff_id`
|
||
4. WHEN Tenant_Admin 禁用某用户账号, THE Backend_API SHALL 将 `auth.users.status` 设为 `disabled`,该用户后续登录小程序时 SHALL 被拒绝
|
||
5. IF Tenant_Admin 尝试将用户的 site_id 修改为不在其管辖范围内的值, THEN THE Backend_API SHALL 返回 403 状态码
|
||
|
||
### 需求 5:Excel 上传 — 文件解析与格式校验
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望上传 Excel 文件并获得即时的格式校验反馈,以便在数据写入前发现和修正错误。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 选择模板类型并上传 Excel 文件, THE Backend_API SHALL 解析文件内容,按对应模板的列定义进行格式校验,并返回校验结果
|
||
2. THE Backend_API SHALL 支持 4 种模板类型的格式校验:
|
||
- 财务支出(expense):月份(YYYY-MM,不超过当前月)、支出类别(枚举 8 值:房租/水电/物业/食品饮料进货/耗材/报销/固定人员工资/其他费用)、金额(> 0,精度 2 位小数)、备注(可选,最长 500 字符)
|
||
- 团购收入(platform_income):月份(YYYY-MM)、平台名称(非空)、收入金额(> 0)、备注(可选,最长 500 字符)
|
||
- 助教奖罚(salary_adj):月份(YYYY-MM)、助教姓名(非空)、助教编号(非空)、类型(枚举:扣款/奖金)、金额(> 0)、原因(非空,最长 200 字符)
|
||
- 充值业绩归属(recharge_commission):充值日期(YYYY-MM-DD)、会员名称(非空)、充值金额(> 0)、归属助教(非空)、奖励金额(≥ 0)
|
||
3. WHEN 校验发现格式错误行, THE Backend_API SHALL 返回错误行号、列名和具体错误描述,Tenant_Admin_Web SHALL 标红展示错误行
|
||
4. WHEN 校验全部通过, THE Backend_API SHALL 创建 `biz.excel_upload_log` 记录(状态为 pending),返回 upload_id 和解析后的数据预览
|
||
5. IF 上传文件不是有效的 Excel 格式(.xlsx/.xls), THEN THE Backend_API SHALL 返回 400 状态码并提示文件格式错误
|
||
|
||
### 需求 6:Excel 上传 — 人员匹配校验
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望上传助教奖罚和充值业绩归属数据时,系统能自动校验助教信息的准确性,以减少数据录入错误。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN 上传助教奖罚(salary_adj)或充值业绩归属(recharge_commission)模板时, THE Backend_API SHALL 对每行数据中的助教姓名+助教编号执行匹配校验
|
||
2. THE Backend_API SHALL 按以下顺序匹配:先查 `fdw_etl.v_dim_assistant`(nickname + assistant_number,`scd2_is_current=1`),如不匹配再查 `fdw_etl.v_dim_staff` + `v_dim_staff_ex`(name + staff_number)
|
||
3. WHEN 匹配成功, THE Backend_API SHALL 在返回数据中填充 `assistant_id`
|
||
4. WHEN 匹配失败, THE Backend_API SHALL 标记该行为校验警告(warning),不阻断上传流程,但在前端以黄色高亮提示 Tenant_Admin 确认
|
||
5. THE Tenant_Admin_Web SHALL 在校验结果页面汇总展示:通过行数、警告行数、错误行数
|
||
|
||
### 需求 7:Excel 上传 — 冲突检测与解决
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望在数据写入前看到与已有数据的冲突情况,并能逐行选择处理方式,以避免误覆盖历史数据。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN 格式校验通过后, THE Backend_API SHALL 按各模板的主键规则检测冲突:
|
||
- 财务支出:月份 + 支出类别
|
||
- 团购收入:月份 + 平台名称
|
||
- 助教奖罚:月份 + 助教姓名 + 助教编号 + 类型 + 原因
|
||
- 充值业绩归属:充值日期 + 会员名称 + 归属助教
|
||
2. WHEN 检测到冲突行(主键已存在), THE Backend_API SHALL 返回 diff 数据(旧值 vs 新值,按字段逐一对比)
|
||
3. THE Tenant_Admin_Web SHALL 展示 diff 交互表格,每行显示字段名、旧值、新值和操作选项(替换/保留),支持"全部替换"和"全部保留"快捷操作
|
||
4. WHEN Tenant_Admin 确认冲突解决方案并提交, THE Backend_API SHALL 接受 upload_id 和每行的解决方案(resolutions 数组),按选择执行写入
|
||
5. WHEN 无冲突行, THE Backend_API SHALL 直接标记为"待写入",Tenant_Admin 确认后写入
|
||
|
||
### 需求 8:Excel 上传 — 数据写入与记录
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望确认后的数据能正确写入目标表,并保留上传历史记录以便追溯。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 确认写入, THE Backend_API SHALL 将数据写入对应目标表:
|
||
- 助教奖罚 → `biz.salary_adjustments`(直接写入业务库)
|
||
- 财务支出 → `biz.stg_finance_expense`(staging 表,由 ETL 同步到 DWS)
|
||
- 团购收入 → `biz.stg_platform_income`(staging 表)
|
||
- 充值业绩归属 → `biz.stg_recharge_commission`(staging 表)
|
||
2. THE Backend_API SHALL 在写入完成后更新 `biz.excel_upload_log` 记录:状态设为 `confirmed`、记录实际写入行数、冲突解决数、确认时间
|
||
3. IF 写入过程中发生数据库错误, THEN THE Backend_API SHALL 回滚整个批次的写入,将 `biz.excel_upload_log` 状态设为 `failed`,记录错误详情到 `error_detail` 字段
|
||
4. WHEN Tenant_Admin 查看上传记录页面, THE Backend_API SHALL 返回管辖门店范围内的历史上传记录列表,包含模板类型、文件名、上传人、上传时间、行数、冲突数、状态,支持分页
|
||
5. WHEN Tenant_Admin 下载空白模板, THE Backend_API SHALL 返回对应模板类型的 Excel 文件(含表头和格式说明)
|
||
|
||
### 需求 9:维客线索 — 客户搜索
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望通过客户姓名或手机号搜索客户,以便查看和管理其维客线索。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 输入搜索关键词, THE Backend_API SHALL 在管辖门店范围内搜索客户,匹配 `fdw_etl.v_dim_member` 的 `nickname`(模糊匹配)或 `mobile`(精确匹配),返回客户列表(member_id、姓名、手机号脱敏、所属门店)
|
||
2. THE Backend_API SHALL 通过 `member_id` JOIN `fdw_etl.v_dim_member`(`scd2_is_current=1`)获取客户姓名和手机号,不使用结算单上的冗余字段(DQ-6 规则)
|
||
3. WHEN Tenant_Admin 选择门店筛选条件, THE Backend_API SHALL 在搜索结果中仅返回该门店的客户
|
||
4. IF 搜索结果为空, THEN THE Tenant_Admin_Web SHALL 展示"未找到匹配客户"的提示
|
||
|
||
### 需求 10:维客线索 — 线索列表与展示
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望查看某客户的全部维客线索,以便了解客户画像和历史记录。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 选择某客户, THE Backend_API SHALL 返回该客户在管辖门店范围内的全部维客线索列表,包含:线索 ID、大类标签(category)、摘要(summary)、详情(detail)、提供人(recorded_by_name)、来源(source:manual / ai_consumption / ai_note)、记录时间(recorded_at)、隐藏状态(is_hidden)
|
||
2. THE Tenant_Admin_Web SHALL 按大类标签分组展示线索,支持按来源和隐藏状态筛选
|
||
3. THE Tenant_Admin_Web SHALL 对已隐藏的线索以灰色或删除线样式区分展示
|
||
|
||
### 需求 11:维客线索 — 编辑
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望修改维客线索的标签、摘要和详情,以便纠正错误或补充信息。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 编辑某条线索, THE Backend_API SHALL 接受修改后的 category(枚举 6 值:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈)、summary(非空,最长 200 字符)、detail(可选)
|
||
2. THE Backend_API SHALL 验证 category 值在枚举范围内,summary 非空且不超过长度限制
|
||
3. IF 线索 ID 不存在或不在管辖门店范围内, THEN THE Backend_API SHALL 返回 404 状态码
|
||
|
||
### 需求 12:维客线索 — 删除
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望删除错误或无效的维客线索,以保持数据整洁。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 请求删除某条线索, THE Tenant_Admin_Web SHALL 弹出二次确认对话框,明确提示"删除后不可恢复"
|
||
2. WHEN Tenant_Admin 确认删除, THE Backend_API SHALL 对该线索执行物理删除(`DELETE FROM public.member_retention_clue WHERE id = ?`)
|
||
3. IF 线索 ID 不存在或不在管辖门店范围内, THEN THE Backend_API SHALL 返回 404 状态码
|
||
|
||
### 需求 13:维客线索 — 隐藏与显示
|
||
|
||
**用户故事:** 作为 Tenant_Admin,我希望隐藏某些线索使其不在小程序端展示,同时保留在管理后台可见,以便灵活控制线索的可见性。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Tenant_Admin 切换某条线索的隐藏状态, THE Backend_API SHALL 更新 `public.member_retention_clue.is_hidden` 字段(true=隐藏,false=显示)
|
||
2. WHILE 线索的 `is_hidden = true`, THE Backend_API SHALL 确保小程序端查询线索时通过 `WHERE is_hidden = false` 条件过滤该线索
|
||
3. THE Tenant_Admin_Web SHALL 允许 Tenant_Admin 将已隐藏的线索恢复为显示状态(`is_hidden = false`)
|
||
4. IF 线索 ID 不存在或不在管辖门店范围内, THEN THE Backend_API SHALL 返回 404 状态码
|
||
|
||
### 需求 14:管理后台 — 租户管理员账号管理
|
||
|
||
**用户故事:** 作为 Operator,我希望在系统管理后台(admin-web)中创建、编辑和管理租户管理员账号,以便控制谁可以登录租户管理后台。
|
||
|
||
#### 验收标准
|
||
|
||
1. WHEN Operator 打开租户管理员管理页面, THE admin-web SHALL 展示所有租户管理员列表,包含用户名、显示名称、管辖门店、账号状态(启用/禁用)、创建时间、最后登录时间,支持分页和关键词搜索
|
||
2. WHEN Operator 创建新的租户管理员, THE Backend_API SHALL 接受用户名(唯一)、初始密码、显示名称、tenant_id、managed_site_ids(门店列表),将密码 bcrypt 哈希后写入 `auth.tenant_admins`,记录 `created_by` 为当前 Operator 的 user_id
|
||
3. IF 用户名已存在, THEN THE Backend_API SHALL 返回 409 状态码并提示"用户名已存在"
|
||
4. WHEN Operator 编辑租户管理员信息, THE Backend_API SHALL 允许修改以下字段:显示名称、managed_site_ids、is_active(启用/禁用)
|
||
5. WHEN Operator 重置租户管理员密码, THE Backend_API SHALL 接受新密码,bcrypt 哈希后更新 `auth.tenant_admins.password_hash`
|
||
6. IF Operator 禁用某租户管理员(is_active=false), THEN 该管理员后续登录租户管理后台时 SHALL 被拒绝(返回 403)
|
||
7. THE Backend_API SHALL 要求 Operator 具有 site_admin 或 tenant_admin 角色才能访问租户管理员管理端点
|
||
|
||
### 需求 15:数据库变更 — 新建表
|
||
|
||
**用户故事:** 作为开发者,我需要创建 NS4 所需的新数据库表,以支撑租户管理后台的全部功能。
|
||
|
||
#### 验收标准
|
||
|
||
1. THE 迁移脚本 SHALL 在 `auth` Schema 中创建 `tenant_admins` 表,包含字段:id(BIGSERIAL PK)、username(VARCHAR(50) UNIQUE NOT NULL)、password_hash(VARCHAR(255) NOT NULL)、display_name(VARCHAR(100))、tenant_id(BIGINT NOT NULL)、managed_site_ids(BIGINT[] NOT NULL)、is_active(BOOLEAN DEFAULT true)、created_by(BIGINT)、created_at(TIMESTAMPTZ DEFAULT NOW())、last_login_at(TIMESTAMPTZ),并创建 tenant_id 索引
|
||
2. THE 迁移脚本 SHALL 在 `biz` Schema 中创建 `salary_adjustments` 表,包含字段:id(BIGSERIAL PK)、site_id(BIGINT NOT NULL)、assistant_id(BIGINT 可空)、assistant_name(VARCHAR(100) NOT NULL)、assistant_number(VARCHAR(50) NOT NULL)、salary_month(VARCHAR(7) NOT NULL)、adjustment_type(CHECK IN deduction/bonus)、amount(NUMERIC(12,2) > 0)、reason(VARCHAR(200) NOT NULL)、upload_batch_id(FK → excel_upload_log)、created_at、created_by,并创建 (site_id, salary_month) 和 (assistant_id, salary_month) 索引
|
||
3. THE 迁移脚本 SHALL 在 `biz` Schema 中创建 `excel_upload_log` 表,包含字段:id(BIGSERIAL PK)、site_id(BIGINT NOT NULL)、upload_type(CHECK IN expense/platform_income/salary_adj/recharge_commission)、file_name(VARCHAR(255) NOT NULL)、uploaded_by(BIGINT NOT NULL)、row_count(INTEGER DEFAULT 0)、conflict_count(INTEGER DEFAULT 0)、resolved_count(INTEGER DEFAULT 0)、status(CHECK IN pending/confirmed/failed)、error_detail(JSONB)、created_at、confirmed_at,并创建 (site_id, created_at DESC) 索引
|
||
4. THE 迁移脚本 SHALL 在 `biz` Schema 中创建 3 张 staging 表:`stg_finance_expense`、`stg_platform_income`、`stg_recharge_commission`,各表包含 site_id、业务字段、upload_batch_id(FK)、synced_at(TIMESTAMPTZ 可空,NULL 表示未同步)、created_at
|
||
|
||
### 需求 16:数据库变更 — 表结构修改
|
||
|
||
**用户故事:** 作为开发者,我需要为现有表添加 NS4 所需的字段,以支持维客线索隐藏功能。
|
||
|
||
#### 验收标准
|
||
|
||
1. THE 迁移脚本 SHALL 为 `public.member_retention_clue` 表添加 `is_hidden` 列(BOOLEAN NOT NULL DEFAULT false),并添加列注释说明"是否隐藏(true=管理后台保留但小程序不展示)"
|
||
2. THE 迁移脚本 SHALL 确保已有数据的 `is_hidden` 值为 false(通过 DEFAULT 约束保证)
|
||
3. THE 小程序端现有的线索查询 API SHALL 在查询条件中增加 `WHERE is_hidden = false`,确保隐藏线索不在小程序端展示
|
||
|
||
### 需求 17:前端应用骨架
|
||
|
||
**用户故事:** 作为开发者,我需要搭建租户管理后台的前端项目骨架,以便后续功能页面的开发。
|
||
|
||
#### 验收标准
|
||
|
||
1. THE 前端项目 SHALL 在 `apps/tenant-admin/` 目录下创建,使用 React + Vite + Ant Design 技术栈,包含 `package.json`、`vite.config.ts`、`tsconfig.json` 等配置文件
|
||
2. THE Tenant_Admin_Web SHALL 提供侧边栏导航,包含四个功能模块入口:用户审核、用户管理、Excel 上传、维客线索管理
|
||
3. THE Tenant_Admin_Web SHALL 实现登录页面,未认证时自动重定向到登录页
|
||
4. THE Tenant_Admin_Web SHALL 封装统一的 API 调用层(`services/api.ts`),处理 JWT 令牌的自动附加、刷新和过期重定向
|
||
5. THE Tenant_Admin_Web SHALL 使用与 Backend_API 一致的响应格式(`{ code: 0, data: ... }`),Pydantic 使用 `alias_generator=to_camel` 实现驼峰命名转换
|
||
|
||
### 需求 18:后端路由模块
|
||
|
||
**用户故事:** 作为开发者,我需要在 FastAPI 后端中创建租户管理专用的路由模块,以提供 NS4 所需的全部 API 端点。
|
||
|
||
#### 验收标准
|
||
|
||
1. THE Backend_API SHALL 在 `apps/backend/app/routers/` 下创建 4 个路由文件:`tenant_auth.py`(登录/JWT 签发/鉴权)、`tenant_users.py`(用户审核+用户管理)、`tenant_excel.py`(Excel 上传/校验/冲突处理)、`tenant_clues.py`(维客线索管理),以及 1 个管理端路由文件 `admin_tenant_admins.py`(租户管理员 CRUD)
|
||
2. THE Backend_API SHALL 将所有租户管理端点注册在 `/api/tenant/` 路径前缀下,管理端租户管理员 CRUD 端点注册在 `/api/admin/tenant-admins/` 路径前缀下,与小程序端点(`/api/xcx/`)隔离
|
||
3. THE Backend_API SHALL 为所有 `/api/tenant/*` 端点(登录接口除外)添加 `require_tenant_admin()` 认证依赖,验证 JWT 的 `aud` 字段为 `tenant-admin`;为 `/api/admin/tenant-admins/*` 端点添加 `_require_admin()` 认证依赖,验证 site_admin 或 tenant_admin 角色
|
||
4. THE Backend_API SHALL 遵循现有的 API 响应格式约定:成功返回 `{ code: 0, data: ... }`,失败返回 `{ code: number, message: string }`,分页返回 `{ items: T[], total: number, page: number, pageSize: number }`
|