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