furniture-3d-system-Wu/furniture-3d-system/docs/架构设计/店铺账号管理取数与筛选逻辑.md

22 KiB
Raw Blame History

店铺账号管理取数与筛选逻辑

用途:给需要接入店铺账号管理数据的开发人员使用。本文描述当前代码和数据库的真实口径。

重要:本文不包含数据库连接串、账号、密码、加密密钥或任何业务明文数据。当前数据由本系统人工维护,不直接从外部数据库同步。

1. 数据源与责任边界

  • 权威数据源:本系统使用 DATABASE_URL 指向的 PostgreSQL 数据库,数据通过本系统店铺账号管理页面/API 人工新增、编辑、导入和维护。
  • 不属于数据源的内容:聚水潭、京东、京喜等外部系统目前不是本模块的自动同步来源;不能假设外部系统中存在同一份实时数据。
  • 推荐接入方式:优先调用本系统鉴权 API。若确实需要数据库直读,应由数据库管理员单独创建只读账号或只读视图,并按本文的字段白名单开放;不要复用应用账号,也不要把应用数据库密码或加密密钥交给外部系统。
  • 人工维护原则:不要直接写入业务表绕过 Service。直接写入会跳过页面权限、字段权限、密文加密、唯一键校验和操作日志。

1.1 代码与迁移依据

当前数据契约以以下文件为准:

  • Prisma 模型:prisma/schema.prisma 中 StoreAccount、StoreAccountStoreProfile、StoreAccountChangeLog、StoreAccountFieldPermission、SupplyChainPermissionOverride。
  • 建表迁移:prisma/migrations/20260826120000_add_store_account_management/migration.sql。
  • 店铺主体和银行卡字段、平台资料表迁移:prisma/migrations/20260828100000_extend_store_account_profiles/migration.sql。
  • 列表筛选规则:src/server/repositories/store-account-management.repository.ts。
  • 业务校验、脱敏和日志:src/server/services/store-account-management.service.ts。
  • 导出字段:src/server/services/store-account-management-export.service.ts。

2. 物理表清单与关联关系

PostgreSQL 物理表名使用双引号保留大小写,取数时应写成下表中的名称。

物理表 用途 主要关联键 是否主业务数据
"StoreAccount" 店铺账号主记录,一行代表一个店铺下的一种账号 id;业务唯一键为 storeName + accountType + account 是
"StoreAccountStoreProfile" 京东/京喜店铺资料,一行代表一个店铺的一个平台资料 逻辑关联 storeName,平台由 platform 区分 是(扩展资料)
"StoreAccountChangeLog" 新增、编辑、导入、查看敏感字段、资料维护的审计日志 storeAccountId 可为空;资料日志用 storeName 否(审计)
"StoreAccountFieldPermission" 店铺账号字段对业务类型/个人的可见、只读、可编辑配置 fieldKey + subjectKey 唯一 否(权限配置)
"SupplyChainPermissionOverride" 页面级的查看、下载、编辑权限 targetKey + subjectKey + action 唯一;本模块 targetKey = 'storeAccountManagement' 否(权限配置)

StoreAccountStoreProfile 与 StoreAccount 没有数据库外键,应用层按 storeName 关联。因此外部查询时要注意:资料表可能没有对应账号行,也可能一个店铺有多个账号行;不要用 id 去连接资料表。

3. "StoreAccount" 字段字典

字段 PostgreSQL 类型/约束 业务含义 空值与敏感性
id TEXT,主键 账号记录 ID 系统生成,不为空
storeName TEXT NOT NULL 店铺名称,也是资料表的逻辑关联键 不应为空;支持多选筛选
operationOwner TEXT NOT NULL DEFAULT '' 店铺运营负责人 空字符串表示未维护
operationDepartmentHead TEXT NOT NULL DEFAULT '' 店铺运营部门负责人 空字符串表示未维护
operationDirector TEXT NOT NULL DEFAULT '' 店铺运营总监 空字符串表示未维护
profitCenter TEXT NOT NULL DEFAULT '' 店铺所属利润中心 空字符串或:运营中心A、运营中心B、运营中心C、运营中心G
subject TEXT NOT NULL DEFAULT '' 店铺主体 空字符串表示未维护
accountType TEXT NOT NULL 账号类型 正常值 MAIN(主账号)、SUB(子账号);不完整导入允许空字符串或 /
account TEXT NOT NULL 登录账号 新增时必填
passwordCiphertext TEXT NOT NULL DEFAULT '' 密码密文 绝不作为普通取数字段返回;空字符串表示未设置
boundBankAccountNumberCiphertext TEXT NOT NULL DEFAULT '' 店铺绑定银行卡号密文 与密码同等级敏感;空字符串表示未设置
boundPhone TEXT NOT NULL DEFAULT '' 绑定手机号 空字符串表示未维护
verificationContact TEXT NOT NULL DEFAULT '' 手机号验证码对接人 空字符串表示未维护
jstOrderService TEXT NOT NULL DEFAULT '' 聚水潭订单服务号 空字符串表示未维护
accountGroupName TEXT NOT NULL DEFAULT '' 账号分组名称 空字符串表示未维护
createdById TEXT 可空 首次创建人 ID 系统审计字段
updatedById TEXT 可空 最近修改人 ID 系统审计字段
createdAt TIMESTAMP(3) NOT NULL 账号记录创建时间 系统生成;列表按此字段倒序
updatedAt TIMESTAMP(3) NOT NULL 账号记录最近修改时间 系统自动更新时间

约束和索引:

  • 唯一约束:("storeName", "accountType", "account"),导入按这三个字段执行 upsert。
  • 索引:storeName、account、accountType、updatedAt。
  • 本表没有软删除字段;空白字段是空字符串,不使用“系统默认值”兜底。

4. "StoreAccountStoreProfile" 字段字典

该表用于“京东/京喜店铺资料”次页面的后续维护,不要求在账号主表导入时同时填写。

字段 PostgreSQL 类型/约束 业务含义 取值规则
id TEXT,主键 店铺平台资料 ID 系统生成
storeName TEXT NOT NULL 店铺名称 与 StoreAccount.storeName 逻辑关联
platform TEXT NOT NULL 平台代码 JD=京东,JX=京喜
merchantNumber TEXT NOT NULL DEFAULT '' 商户号(钱包商户号) 空字符串表示未维护
secondaryMerchantNumber TEXT NOT NULL DEFAULT '' 二级商户号 空字符串表示未维护
merchantStoreId TEXT NOT NULL DEFAULT '' 商家 ID(钱包店铺号) 空字符串表示未维护
companyId TEXT NOT NULL DEFAULT '' 公司 ID(京麦店铺 ID) 空字符串表示未维护
createdById TEXT 可空 首次创建人 ID 系统审计字段
updatedById TEXT 可空 最近修改人 ID 系统审计字段
createdAt TIMESTAMP(3) NOT NULL 资料创建时间 系统生成
updatedAt TIMESTAMP(3) NOT NULL 资料最近修改时间 系统自动更新时间

约束和平台规则:

  • 唯一约束:("storeName", "platform"),同一店铺同一平台只有一条资料。
  • 店铺名以 京东- 开头时只能使用 JD;以 京喜- 开头时只能使用 JX。
  • 非 京东- 或 京喜- 开头的店铺不支持店铺资料页面/API,服务端会拒绝读取或保存。
  • 资料字段通过 storeName 关联账号主表,导出时会按平台展开为京东四列和京喜四列。

5. "StoreAccountChangeLog" 字段与日志格式

字段 PostgreSQL 类型/约束 含义
id TEXT,主键 日志 ID
storeAccountId TEXT 可空 账号记录 ID;店铺资料变更时为空
storeName TEXT NOT NULL 店铺名称
operation TEXT NOT NULL 操作类型
actorId TEXT 可空 操作人 ID
actorName TEXT NOT NULL 操作人名称
changes JSONB NOT NULL 字段变更明细数组
createdAt TIMESTAMP(3) NOT NULL 日志时间

当前 operation 代码:

operation 含义
CREATE 新增账号
UPDATE 编辑账号
IMPORT_CREATE 导入新增
IMPORT_UPDATE 导入更新
BATCH_UPDATE 批量修改
VIEW_PASSWORD 查看密码
VIEW_BANK_ACCOUNT 查看银行卡号
PROFILE_UPDATE 修改京东/京喜店铺资料

changes 的标准元素为:

{
  "field": "operationOwner",
  "previousValue": "原值",
  "nextValue": "新值"
}

特殊字段和资料字段:

  • password、boundBankAccountNumber 只记录“已设置/未设置”,不写入明文或密文。
  • 查看敏感字段时记录 nextValue: "已查看"。
  • 平台资料字段使用 JD.merchantNumber、JX.companyId 这样的字段名。
  • API 返回日志前还会根据当前调用人的字段权限过滤隐藏字段。

6. "StoreAccountFieldPermission" 字段字典

字段 PostgreSQL 类型/约束 含义
id TEXT,主键 权限记录 ID
fieldKey TEXT NOT NULL 店铺账号字段 key,如 account、password、boundBankAccountNumber
subjectKey TEXT NOT NULL 业务类型或个人主体
visibility TEXT NOT NULL DEFAULT 'READONLY' HIDDEN、READONLY 或 EDITABLE
updatedById TEXT 可空 最后配置人 ID
createdAt / updatedAt TIMESTAMP(3) 权限记录时间

当前可配置的业务类型 subjectKey:

design(设计)、planning(企划)、brandCenter(策划)、purchase(采购)、merchandiser(采购-跟单)、pricing(采购-核价)、quality(采购-质检)、operation(运营)、warehouse(仓库)、customerService(客服)、management(经管)、viewer(普通用户)。

个人例外使用 user@<用户ID>。唯一约束为 ("fieldKey", "subjectKey")。createdAt、updatedAt 是系统字段,不能通过字段权限配置为可编辑。

7. 页面级权限表

"SupplyChainPermissionOverride" 的 targetKey 固定为 storeAccountManagement,action 为 view、download、edit。

场景 当前规则
未登录 所有店铺账号接口返回 401
页面查看 需要工作台可见且 storeAccountManagement:view 生效
新增、编辑、导入、批量修改 需要页面 edit
导出全部或已选 需要页面可见,且调用人是管理员或经管
配置字段权限 需要页面可见,且调用人是管理员或经管
密码/银行卡查看 还要通过对应字段权限;服务端单独记录查看日志

字段可见性解析顺序:

  1. 页面没有 view 时,所有字段均为 HIDDEN。
  2. 先取页面编辑能力形成基础值:系统时间字段只读,密码和银行卡默认隐藏,普通字段按页面编辑能力为可编辑或只读。
  3. 同一字段的业务类型配置按 EDITABLE > READONLY > HIDDEN 合并。
  4. user@<用户ID> 的个人配置覆盖业务类型配置。
  5. 没有页面编辑权时,即使字段配置为 EDITABLE 也会降为只读;系统时间字段永远不会变成可编辑。

列表接口对隐藏字段返回空字符串;密码和银行卡只返回 passwordConfigured、boundBankAccountNumberConfigured 布尔值,不返回密文。

8. 列表取数与筛选逻辑

8.1 API 参数

GET /api/supply-chain/store-account-management

参数 默认/限制 逻辑
page 默认 1,最小 1 页码
pageSize 默认 50,范围 1..200 每页行数;页面常用 50/100/200
search 最多 120 字符 关键词,可用空格、逗号、中文逗号、分号分隔多个 token
storeName 最多 120 字符 店铺名称多选值,同样支持逗号、中文逗号、分号分隔
accountType 空、MAIN/SUB 或显示名 包含匹配;页面显示主账号/子账号
operationOwner 空或文本 店铺运营负责人包含匹配
accountGroupName 空或文本 账号分组名称包含匹配
createdAtGranularity year、month、day 创建时间筛选粒度
createdAtValue 年 YYYY;月 YYYY-MM;日 YYYY-MM-DD 与粒度配套的值

8.2 条件组合

  • 不同筛选项之间是 AND。
  • search 内部每个 token 会对以下字段做 OR:storeName、account、operationOwner、operationDepartmentHead、operationDirector、profitCenter、subject、boundPhone、verificationContact、jstOrderService、accountGroupName。
  • search 不搜索密码、银行卡、创建时间、修改时间和京东/京喜资料字段。
  • storeName 会拆分并去重,使用精确 IN 匹配;最多取 500 个店铺名。
  • accountType、operationOwner、accountGroupName 使用不区分大小写的 contains 匹配。
  • 创建时间使用中国时区 +08:00 的半开区间:[开始时间, 结束时间)。例如 2026-08 是 2026-08-01 00:00:00+08:00(含)到 2026-09-01 00:00:00+08:00(不含)。

8.3 排序与分页

固定排序:

ORDER BY "createdAt" DESC, "id" ASC

因此最新创建的记录永远在前面;id ASC 只用于创建时间相同时稳定排序。分页使用 OFFSET (page - 1) * pageSize LIMIT pageSize,同时查询总数。

成功响应核心结构:

{
  "success": true,
  "data": {
    "rows": [],
    "storeNames": ["京东-示例店"],
    "pageInfo": { "page": 1, "pageSize": 50, "total": 0, "totalPages": 1 },
    "fieldAccess": {}
  },
  "meta": { "requestId": "...", "timestamp": "..." }
}

storeNames 是全表去重后按名称升序返回的店铺列表,用于前端多选;它不受当前页面筛选条件限制。

9. 只读 SQL 示例

以下 SQL 仅用于说明字段和筛选口径,执行账号必须是单独的只读账号。示例不包含密码密文列。

9.1 最新账号列表

SELECT
  "id", "storeName", "operationOwner", "operationDepartmentHead",
  "operationDirector", "profitCenter", "subject", "accountType",
  "account", "boundPhone", "verificationContact", "jstOrderService",
  "accountGroupName", "createdAt", "updatedAt"
FROM "StoreAccount"
ORDER BY "createdAt" DESC, "id" ASC
LIMIT 50 OFFSET 0;

9.2 店铺 + 账号类型 + 月份筛选

SELECT
  "id", "storeName", "accountType", "account", "createdAt", "updatedAt"
FROM "StoreAccount"
WHERE "storeName" IN ('京东-示例店', '京喜-示例店')
  AND "accountType" = 'MAIN'
  AND "createdAt" >= TIMESTAMPTZ '2026-08-01 00:00:00+08:00'
  AND "createdAt" <  TIMESTAMPTZ '2026-09-01 00:00:00+08:00'
ORDER BY "createdAt" DESC, "id" ASC;

9.3 关键词筛选的 SQL 等价写法

WHERE (
  "storeName" ILIKE '%' || $1 || '%'
  OR "account" ILIKE '%' || $1 || '%'
  OR "operationOwner" ILIKE '%' || $1 || '%'
  OR "operationDepartmentHead" ILIKE '%' || $1 || '%'
  OR "operationDirector" ILIKE '%' || $1 || '%'
  OR "profitCenter" ILIKE '%' || $1 || '%'
  OR "subject" ILIKE '%' || $1 || '%'
  OR "boundPhone" ILIKE '%' || $1 || '%'
  OR "verificationContact" ILIKE '%' || $1 || '%'
  OR "jstOrderService" ILIKE '%' || $1 || '%'
  OR "accountGroupName" ILIKE '%' || $1 || '%'
)

多个 token 时,把每个 token 的上述括号用 AND 连接。

9.4 查询平台资料

SELECT
  "storeName", "platform", "merchantNumber", "secondaryMerchantNumber",
  "merchantStoreId", "companyId", "createdAt", "updatedAt"
FROM "StoreAccountStoreProfile"
WHERE "storeName" = $1
  AND "platform" IN ('JD', 'JX')
ORDER BY "platform" ASC;

资料缺失时不要补默认值;没有行或字段为空都表示尚未维护。

9.5 查询审计日志

SELECT
  "id", "storeAccountId", "storeName", "operation",
  "actorId", "actorName", "changes", "createdAt"
FROM "StoreAccountChangeLog"
WHERE "storeName" = $1
ORDER BY "createdAt" DESC
LIMIT 200;

10. 当前 API 接口清单

所有接口都要求登录,并返回统一的 { success, data/error, meta } 包装。接口权限还会检查工作台权限和 storeAccountManagement 页面权限。

方法 路径 用途 主要限制
GET /api/supply-chain/store-account-management 分页列表、筛选、店铺名称选项 页面 view
POST /api/supply-chain/store-account-management 新增账号 页面 edit;店铺名、账号类型、账号必填
GET /api/supply-chain/store-account-management/{id} 单条账号 页面 view
PATCH /api/supply-chain/store-account-management/{id} 编辑账号 页面 edit;只更新有权限的字段
POST /api/supply-chain/store-account-management/import Excel/CSV 导入账号 页面 edit;10 MB、5000 行;NDJSON 进度
POST /api/supply-chain/store-account-management/batch 批量修改前预览 页面 edit + 目标字段 EDITABLE;最多 500 条
PATCH /api/supply-chain/store-account-management/batch 批量修改 同上;密码、银行卡和系统时间不可批量修改
GET /api/supply-chain/store-account-management/export 导出全部账号 仅管理员或经管;包含明文密码/银行卡,必须严格控制调用方
GET /api/supply-chain/store-account-management/export?scope=selected&ids=id1,id2 导出已选账号 仅管理员或经管;最多 200 个 ID
GET /api/supply-chain/store-account-management/logs?id=... 查询单账号日志 页面 view;返回内容按字段权限过滤
GET /api/supply-chain/store-account-management/logs?storeName=... 查询店铺日志 页面 view;最多返回 200 条
GET /api/supply-chain/store-account-management/{id}/password 查看密码明文 字段可见;响应不缓存,并写查看日志
GET /api/supply-chain/store-account-management/{id}/bank-account 查看银行卡号明文 字段可见,并写查看日志
GET /api/supply-chain/store-account-management/permissions 读取字段权限配置 仅管理员或经管
PUT /api/supply-chain/store-account-management/permissions 保存字段权限配置 仅管理员或经管;最多 500 条更新
GET /api/supply-chain/store-account-management/profiles?storeName=... 读取京东/京喜店铺资料 页面 view;店铺名前缀必须匹配
PATCH /api/supply-chain/store-account-management/profiles 保存平台资料 页面 edit;平台必须与店铺名前缀一致
GET /api/supply-chain/store-account-management/profiles/template?platform=JD|JX 下载平台资料模板 页面 edit
POST /api/supply-chain/store-account-management/profiles/import 导入平台资料 页面 edit;表单字段 file + platform,10 MB、5000 行

导出文件为 XLSX,包含账号主表字段、解密后的密码和绑定银行卡号,以及按 JD/JX 展开的八个店铺资料字段。外部系统若不需要敏感字段,不应调用导出接口,应该调用列表接口或使用只读白名单视图。

11. 导入、更新和空白值规则

  • 主表导入支持中文表头和内部英文 key;未知列会被忽略。
  • 行数上限 5000,文件上限 10 MB;每一行按 storeName + accountType + account upsert,并写入 IMPORT_CREATE 或 IMPORT_UPDATE 日志。
  • 普通文本字段导入空白会保存为空字符串,表示“未维护”;不会套用系统默认值。
  • 密码和绑定银行卡号为空时不会覆盖已有密文;要清除敏感值需由具备相应字段编辑权限的业务流程明确处理,不能通过普通空白导入误清除。
  • 新增账号时 storeName、accountType、account 必填,accountType 正常应为 MAIN 或 SUB。
  • 批量修改会先返回所选记录当前值预览,再统一更新;每条实际发生变化的记录写一条 BATCH_UPDATE 日志。
  • 平台资料导入只识别“店铺名称”和四个资料字段,按店铺名和平台 upsert;空白资料仍然允许保存。

12. 敏感字段与外部接入注意事项

  1. passwordCiphertext 和 boundBankAccountNumberCiphertext 使用 AES-256-GCM 存储,密钥来自服务端 STORE_ACCOUNT_ENCRYPTION_KEY(生产必须配置至少 32 个字符)。
  2. 外部系统不能自行解密,也不能索要或复制该密钥;密文列不应进入普通数据同步任务、日志或缓存。
  3. 普通列表接口只返回“是否已设置”布尔值;明文只通过受权限保护的查看接口或管理员/经管导出得到,并且会留下审计日志。
  4. 字段权限和页面权限不是同一层:先满足页面 view/edit,再应用业务类型和个人字段权限。
  5. 直连数据库时建议只开放不敏感白名单字段,至少排除两个 *Ciphertext 字段、权限表和日志中的敏感上下文;如需日志,应单独授予审计读取权限。
  6. 当前没有外部只读视图或公开 Token 契约。外部接入前应由系统负责人确认鉴权方式、限流、字段白名单、数据刷新频率和脱敏要求。

13. 接入前检查清单

  • 确认读取的是当前 DATABASE_URL 对应的 PostgreSQL,而不是历史数据库或外部平台库。
  • 使用本文中的双引号物理表名和字段名,确认迁移已执行。
  • 明确只读范围:主表、平台资料、日志、权限配置是否都需要。
  • 明确是否需要京东/京喜资料,按 storeName + platform 读取,不能按账号 id 连接。
  • 明确筛选时区为中国 +08:00,创建时间使用半开区间。
  • 按 createdAt DESC, id ASC 保持与页面相同的排序。
  • 不读取或传播密码、银行卡密文;需要明文时走授权接口并保留审计记录。
  • 不直接写表;新增、编辑、导入和权限变更必须走系统 API 或经负责人批准的维护流程。