diff --git a/docs/ops/2026-02-18__admin__auth-delivery-infrastructure-integration.md b/docs/ops/2026-02-18__admin__auth-delivery-infrastructure-integration.md index 3d60ec48..84187b3c 100644 --- a/docs/ops/2026-02-18__admin__auth-delivery-infrastructure-integration.md +++ b/docs/ops/2026-02-18__admin__auth-delivery-infrastructure-integration.md @@ -1,59 +1,274 @@ # 权限、物流及系统基础设施全量集成报告 ## 摘要 -本次对 Admin 侧进行了大规模的基础设施补齐与核心管理模块重构。完成了权限管理(Auth/RBAC)、物流设置(Delivery Staff/Stations)、系统通用配置(ml_system_configs)以及数据概览(Statistic)与系统维护(Maintain)模块的端到端数据库接入。彻底解决了这些模块此前长期存在的 Mock 问题。 +本次对 Admin 侧进行了基础设施补齐与核心管理模块重构,完成以下闭环: +- 新增系统统一 KV 配置表 `ml_system_configs`,并提供管理端 RPC 读写; +- 建立 RBAC 权限体系(角色/权限/关联表),并以管理端 RPC 提供 CRUD; +- 新增物流资源表(配送员、提货点),并以管理端 RPC 提供 CRUD; +- 将 Admin 侧相关页面从 Mock/静态数据切换为真实 RPC 数据流。 ## 动机 -虽然商城的业务模块(商品、订单等)已基本闭环,但支撑系统运行的“底座”模块(权限控制、系统开关、物流资源等)仍处于静态模拟阶段。为了实现生产级的管理后台,必须建立统一的配置存储体系、完善的权限分配机制以及真实的物流资源管理。 +商城核心业务(商品、订单等)已基本闭环,但系统底座模块(权限控制、系统开关、物流资源)仍长期处于 Mock/静态模拟阶段,无法满足生产环境可审计、可配置、可授权的要求,因此需要补齐数据库资产(Schema/RLS/RPC)与前端服务层。 ## 影响范围 -- **核心底座**:系统配置读写、全站聚合统计。 -- **权限安全**:角色管理、菜单权限树配置、管理员分配。 -- **物流资源**:配送员库、提货点/核销点管理。 -- **应用配置**:公众号、小程序、APP 及 PC 端参数持久化。 +- **数据库**:新增表、启用 RLS、增加 `SECURITY DEFINER` RPC,影响 admin/analytics 权限闭环。 +- **管理端页面**:权限管理(管理员/角色/菜单权限)、物流设置(配送员/提货点)、系统配置、统计概览、系统信息页面。 +- **服务层**:新增 `services/admin/*Service.uts`,统一数据访问出口(页面不再直连底层 client)。 ## 变更清单 +> 以提交 `5d00e3d` 为准(`feat(admin): complete integration of auth, delivery, and system infrastructure modules`)。 -### 1. 数据库资产 (SQL) -- **Schema (10_schema)**: - - `admin/ml_system_configs_v1.sql`: 统一配置表。 - - `user/ak_auth_system_v1.sql`: 角色、权限、关联表 (RBAC)。 - - `delivery/ak_delivery_system_v1.sql`: 配送员、提货点表。 -- **RLS (20_rls)**: - - `admin/ml_system_configs_rls_v1.sql` - - `auth/ak_auth_rls_v1.sql` - - `delivery/ak_delivery_rls_v1.sql` -- **RPC (30_rpc)**: - - **Admin**: `rpc_admin_system_config_get/save`, `rpc_admin_get_overall_stats`, `rpc_admin_get_system_info`。 - - **Auth**: `rpc_admin_get_admin_list`, `rpc_admin_get_role_list/save/delete`, `rpc_admin_get_permission_list/save/delete`。 - - **Delivery**: 配送员管理 (list/save/delete)、提货点管理 (list/save/delete)。 +### 新增文件 +#### 数据库(权威入库:`docs/sql/`) +- Schema(`docs/sql/10_schema/`) + - `docs/sql/10_schema/admin/ml_system_configs_v1.sql` + - `docs/sql/10_schema/user/ak_auth_system_v1.sql` + - `docs/sql/10_schema/delivery/ak_delivery_system_v1.sql` +- RLS(`docs/sql/20_rls/`) + - `docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql` + - `docs/sql/20_rls/auth/ak_auth_rls_v1.sql` + - `docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql` +- RPC(`docs/sql/30_rpc/`) + - Admin + - `docs/sql/30_rpc/admin/rpc_admin_get_overall_stats_v1.sql` + - `docs/sql/30_rpc/admin/rpc_admin_get_system_info_v1.sql` + - `docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql` + - Auth + - `docs/sql/30_rpc/auth/rpc_admin_get_admin_list_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_get_role_list_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_save_role_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_save_permission_v1.sql` + - `docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql` + - Delivery + - `docs/sql/30_rpc/delivery/rpc_admin_get_delivery_staff_list_v1.sql` + - `docs/sql/30_rpc/delivery/rpc_admin_save_delivery_staff_v1.sql` + - `docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql` + - `docs/sql/30_rpc/delivery/rpc_admin_get_delivery_station_list_v1.sql` + - `docs/sql/30_rpc/delivery/rpc_admin_save_delivery_station_v1.sql` + - `docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql` -### 2. 前端服务层 (UTS) -- `services/admin/systemConfigService.uts` (新增) -- `services/admin/authService.uts` (新增) -- `services/admin/deliveryService.uts` (新增) -- `services/admin/maintainService.uts` (新增) +#### 前端服务层(`services/admin/`) +- `services/admin/systemConfigService.uts` +- `services/admin/authService.uts` +- `services/admin/deliveryService.uts` +- `services/admin/maintainService.uts` -### 3. UI 页面重构 (去 Mock) -- **权限类**: `auth/admin.uvue`, `auth/role.uvue`, `auth/permission.uvue`。 -- **物流类**: `delivery/staff.uvue`, `delivery/station.uvue`。 -- **配置类**: `setting/system/config.uvue`, `app/wechat/config.uvue`, `app/routine/config.uvue`, `app/mobile/config.uvue`, `app/pc/config.uvue`。 -- **综合类**: `statistic/index.uvue`, `maintain/sys/info.uvue`。 +### 修改文件 +#### 数据库 +- `docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql` + +#### 页面(`pages/`) +- `pages/mall/admin/setting/auth/admin.uvue` +- `pages/mall/admin/setting/auth/role.uvue` +- `pages/mall/admin/setting/auth/permission.uvue` +- `pages/mall/admin/setting/delivery/staff.uvue` +- `pages/mall/admin/setting/delivery/station.uvue` +- `pages/mall/admin/setting/system/config.uvue` +- `pages/mall/admin/statistic/index.uvue` +- `pages/mall/admin/maintain/sys/info.uvue` +- `pages/mall/admin/app/mobile/config.uvue` +- `pages/mall/admin/app/pc/config.uvue` + +### 删除文件 +- 无 ## 兼容性与风险 -- **数据迁移**:启用 RLS 后,需确保管理员用户在 `ak_users` 中的 `role` 字段准确设置为 `admin`,否则将无法调用管理端 RPC。 -- **逻辑依赖**:配送员和提货点管理依赖于 `ml_orders` 等核心表已存在。 +- **权限口径依赖**:本项目角色字段权威为 `public.ak_users.role`。RPC 入口鉴权依赖该字段(至少 `admin` / `analytics`),若数据不一致会导致管理端接口不可用。 +- **RLS 与 RPC 闭环**:Auth/Delivery 等表启用 RLS 后,若绕开 RPC 进行直接表访问会被拒绝;需确保前端全部走 service -> RPC。 +- **配置键名约束**:`ml_system_configs` 使用 `config_key` 唯一约束;变更 key 会造成读取不到旧配置。 +- **配送员/提货点数据影响**:提货点对 `anon/authenticated` 开放 `SELECT`(受 `status=1` 限制),属于预期的消费者端只读能力。 ## 回滚方案 -1. 数据库:依次 DROP 刚才创建的 20 余个 RPC 函数及 5 张核心业务表。 -2. 代码:通过 `git checkout` 恢复重构的 10 余个页面文件及 Service 目录。 +- **数据库回滚**: + 1. 依次 `DROP FUNCTION IF EXISTS ...` 移除本次新增 RPC(按 `docs/sql/30_rpc/{admin,auth,delivery}/` 列表)。 + 2. `ALTER TABLE ... DISABLE ROW LEVEL SECURITY` 或移除新增 policy(对应 `docs/sql/20_rls/`)。 + 3. `DROP TABLE IF EXISTS` 移除新增表(谨慎,需确认无业务数据依赖)。 +- **代码回滚**: + - `git revert 5d00e3d`(推荐)或 `git checkout -- ` 恢复页面与 service 变更。 ## 验证方式 -1. **统计验证**:进入“数据概览”,确认销售额、用户数等指标非 0 且与数据库一致。 -2. **配置验证**:在“系统设置”修改网站名称并提交,刷新页面确认数据持久化。 -3. **权限验证**:在“角色管理”添加新身份,确认能在“管理员管理”中进行分配。 -4. **物流验证**:添加配送员后,确认列表分页展示正确且支持实时状态切换。 +1. **系统配置**:进入“系统设置”修改任意字段(如网站名称)-> 保存 -> 刷新确认持久化。 +2. **权限管理**: + - 角色管理:新增角色 -> 列表可见; + - 菜单权限:新增权限项 -> 列表可见; + - 管理员列表:能拉取 `rpc_admin_get_admin_list` 并显示角色标签。 +3. **物流设置**:新增配送员/提货点 -> 列表可见;切换启用/显示状态后刷新保持一致。 +4. **统计概览**:进入数据概览页,确认 RPC 返回聚合指标且页面可渲染。 -## 关联规范 -- 遵循 `AGENT_PROJECT_SPEC.md` 规范。 -- 对齐项目统一的角色鉴权与 RPC 分层口径。 +## 关联文档 +- `docs/project_spec/AGENT_PROJECT_SPEC.md`(操作文档规范与 SQL 入库规范) + +> 注意:规范要求对照 `docs/sql/11_roles_and_permissions_strategy.md`,但仓库当前未找到该文件。此处已按规范第 5/7 节口径执行(角色字段 `ak_users.role`、RLS 默认收口、全局访问走 SECURITY DEFINER RPC)。建议后续补齐该策略文档或确认其真实路径,并在此处更新引用。 + +--- + +## SQL 安全评审报告(逐文件) +> 评审结论枚举:Reject / High / OK。评审标准:AGENT_PROJECT_SPEC.md 第 7 节。 + +### Schema +#### SQL 安全评审报告 +- **对象**:`docs/sql/10_schema/admin/ml_system_configs_v1.sql` +- **目标**:提供统一 KV 配置表用于管理端持久化系统/接口配置。 +- **结论**:OK +- **涉及对象**:表 `ml_system_configs`(DDL);无 grants。 +- **RLS**:由对应 RLS 文件启用。 +- **风险点**:低(仅 DDL)。 +- **整改建议**:无。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/10_schema/user/ak_auth_system_v1.sql` +- **目标**:建立 RBAC(角色/权限/关联)基础表。 +- **结论**:OK +- **涉及对象**:`ak_roles`, `ak_permissions`, `ak_admin_roles`, `ak_role_permissions`。 +- **风险点**:低(仅 DDL);注意外键引用 `ak_users`。 +- **整改建议**:可选:为 `ak_roles.updated_at` 增加触发器自动更新(非必须)。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/10_schema/delivery/ak_delivery_system_v1.sql` +- **目标**:新增配送员与提货点基础表。 +- **结论**:OK +- **涉及对象**:`ml_delivery_staff`, `ml_delivery_stations`。 +- **风险点**:低(仅 DDL)。 +- **整改建议**:可选:对 phone 增加格式校验(应用层)。 +- **准入建议**:允许进入 `docs/sql/`。 + +### RLS +#### SQL 安全评审报告 +- **对象**:`docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql` +- **目标**:启用系统配置表 RLS,限制直接表访问。 +- **结论**:OK +- **RLS**:启用;策略以 RPC 为主。 +- **风险点**:低。 +- **整改建议**:如需管理员直连查询,可增加更严格 policy(建议仍走 RPC)。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/20_rls/auth/ak_auth_rls_v1.sql` +- **目标**:启用 RBAC 表 RLS,默认不开放直接访问。 +- **结论**:OK +- **风险点**:低;默认闭网符合规范。 +- **整改建议**:无。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql` +- **目标**:启用物流表 RLS;提货点开放消费者端只读(`status=1`)。 +- **结论**:OK +- **风险点**:中(对 `anon/authenticated` 开放 select);但 `USING (status=1)` 有约束,符合业务需求。 +- **整改建议**:若需进一步限制字段暴露,可改走 RPC 只返回必要字段。 +- **准入建议**:允许进入 `docs/sql/`。 + +### RPC +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/admin/rpc_admin_system_config_save_v1.sql` +- **目标**:保存系统配置(upsert)。 +- **结论**:OK +- **SECURITY DEFINER**:是;`search_path` 固定:是;入口鉴权:是(`role IN ('admin','analytics')`)。 +- **风险点**:低。 +- **整改建议**:可选:对 `p_key` 增加白名单约束(按业务需要)。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/admin/rpc_admin_system_config_get_v1.sql` +- **目标**:读取系统配置。 +- **结论**:OK +- **SECURITY DEFINER**:是;`search_path` 固定:是;入口鉴权:是。 +- **风险点**:低。 +- **整改建议**:无。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/admin/rpc_admin_get_overall_stats_v1.sql` +- **目标**:返回聚合统计指标(概览页)。 +- **结论**:OK +- **风险点**:低(聚合返回)。 +- **整改建议**:确保有合理的索引与时间过滤(如后续扩展)。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/admin/rpc_admin_get_system_info_v1.sql` +- **目标**:返回系统信息(用于维护页)。 +- **结论**:OK +- **风险点**:中(可能暴露服务器信息);需确保返回字段不包含密钥。 +- **整改建议**:后续如需增加字段,必须最小化并脱敏。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_get_admin_list_v1.sql` +- **目标**:分页获取管理员列表(关联角色名)。 +- **结论**:OK +- **风险点**:中(涉及用户表字段);当前仅返回必要字段,未返回敏感信息。 +- **整改建议**:如扩展字段,避免返回手机号/财务信息等。 +- **准入建议**:允许进入 `docs/sql/`。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_get_role_list_v1.sql` +- **目标**:分页获取角色列表。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_save_role_v1.sql` +- **目标**:新增/更新角色。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql` +- **目标**:删除角色。 +- **结论**:OK +- **风险点**:中(物理删除);需注意被引用时的级联行为。 +- **整改建议**:如需审计/恢复,考虑改为软删除。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql` +- **目标**:获取全量权限/菜单列表。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_save_permission_v1.sql` +- **目标**:新增/更新权限/菜单。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql` +- **目标**:删除权限/菜单。 +- **结论**:OK +- **风险点**:中(物理删除 + 级联)。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_get_delivery_staff_list_v1.sql` +- **目标**:分页获取配送员列表。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_save_delivery_staff_v1.sql` +- **目标**:新增/更新配送员。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql` +- **目标**:删除配送员。 +- **结论**:OK +- **风险点**:中(物理删除)。 + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_get_delivery_station_list_v1.sql` +- **目标**:分页获取提货点列表。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_save_delivery_station_v1.sql` +- **目标**:新增/更新提货点。 +- **结论**:OK + +#### SQL 安全评审报告 +- **对象**:`docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql` +- **目标**:删除提货点。 +- **结论**:OK +- **风险点**:中(物理删除)。 + +## 准入结论 +- 本次新增/修改 SQL 未出现硬阻断项(裸放权、破坏性操作、无鉴权 SECURITY DEFINER),评审结论整体为 **OK**,允许进入 `docs/sql/` 作为权威口径。 diff --git a/docs/ops/2026-02-25__admin__soft-delete-standard-refactor.md b/docs/ops/2026-02-25__admin__soft-delete-standard-refactor.md new file mode 100644 index 00000000..86911362 --- /dev/null +++ b/docs/ops/2026-02-25__admin__soft-delete-standard-refactor.md @@ -0,0 +1,90 @@ +# 软删除标准化改造(RPC + RLS)操作文档 + +## 摘要 +本次对管理端涉及的“删除”相关数据库逻辑进行标准化改造: +- 将各模块 RPC 删除行为由物理删除统一切换为软删除(`UPDATE ... SET deleted_at = now(), deleted_by = ...`); +- 对存在依赖关系的对象补齐级联软删除(如:删除客服话术分类时同步软删其下话术等); +- 对各模块 RLS 策略补齐 `deleted_at IS NULL` 过滤条件,使业务侧默认不可见已软删数据; +- 提供执行顺序建议,确保先补齐字段/索引,再应用 RLS/RPC 逻辑。 + +## 动机 +- 统一删除语义,避免误操作导致数据不可恢复。 +- 支持审计追溯(删除时间、删除人)。 +- 通过 RLS 形成“默认过滤”,降低前端/服务层遗漏过滤导致的数据暴露风险。 + +## 影响范围 +- **数据库**:涉及多个域(admin/cms/decoration/delivery/distribution/finance/kefu/user/auth)的 RLS 与 RPC 文件;新增/补齐软删除字段与索引(迁移脚本)。 +- **管理端功能**:所有调用相关 `rpc_admin_*_delete*` 的管理页面,其删除行为从“物理删除”变更为“逻辑删除”。 +- **数据可见性**:RLS 在 SELECT 场景默认排除 `deleted_at IS NOT NULL` 的记录,前端列表/查询将不再返回已软删数据。 + +## 变更清单 +> 本次变更以 `docs/sql/00_meta/12_soft_delete_standard.md` 作为软删除标准口径。 + +### 新增文件 +- `docs/sql/00_meta/11_roles_and_permissions_strategy.md` +- `docs/sql/00_meta/12_soft_delete_standard.md` +- `docs/sql/10_schema/99_soft_delete_migration_v1.sql` + +### 修改文件 +#### RLS(补齐 `deleted_at IS NULL`) +- `docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql` +- `docs/sql/20_rls/cms/ml_cms_rls_v1.sql` +- `docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql` +- `docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql` +- `docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql` +- `docs/sql/20_rls/finance/ml_extract_rls_v1.sql` +- `docs/sql/20_rls/finance/ml_invoices_rls_v1.sql` +- `docs/sql/20_rls/finance/ml_user_bill_rls_v1.sql` +- `docs/sql/20_rls/finance/ml_user_recharge_rls_v1.sql` + +#### RPC(删除改为软删除 + deleted_by 审计 + 必要时级联) +- `docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql` +- `docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql` +- `docs/sql/30_rpc/cms/rpc_admin_article_category_delete_v1.sql` +- `docs/sql/30_rpc/cms/rpc_admin_article_delete_v1.sql` +- `docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql` +- `docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql` +- `docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql` +- `docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql` +- `docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql` +- `docs/sql/30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql` +- `docs/sql/30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql` +- `docs/sql/30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql` +- `docs/sql/30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql` +- `docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql` +- `docs/sql/30_rpc/user/rpc_admin_user_group_delete_v1.sql` +- `docs/sql/30_rpc/user/rpc_admin_user_label_delete_v1.sql` +- `docs/sql/30_rpc/user/rpc_admin_user_level_delete_v1.sql` + +#### 前端(与删除相关页面适配/受影响) +- `pages/mall/admin/setting/delivery/station.uvue` +- `pages/mall/admin/setting/interface/storage.uvue` + +### 删除文件 +- 无 + +## 兼容性与风险 +- **数据不可再“物理删除”释放唯一约束**:软删除后记录仍在表中,若存在唯一索引且未做“partial unique(仅对未删除记录生效)”,可能导致“删除后无法新建同名/同 key”问题,需要按业务决定是否改索引策略。 +- **RLS 策略一致性要求更高**:补齐 `deleted_at IS NULL` 后,任何期望访问回收站数据的场景都需要新增专用策略或通过管理端特权 RPC 实现。 +- **级联链路需覆盖完整**:部分对象存在多级依赖(分类 -> 子项 -> 关联),若漏掉级联,会出现“父对象不可见但子对象仍可见/仍占用数据”的不一致。 + +## 回滚方案 +- **仅回滚删除语义(不建议长期保持)**: + - 将相关 `rpc_admin_*_delete*` 还原为物理删除(`DELETE FROM ...`)并移除 `deleted_at/deleted_by` 写入逻辑。 +- **回滚 RLS 过滤**: + - 在对应 `docs/sql/20_rls/**` 中移除 `AND deleted_at IS NULL`(或恢复到改造前版本)。 +- **回滚 schema 迁移**: + - 不建议回滚 `deleted_*` 字段(会丢失审计数据);如必须回滚,应先评估依赖与历史数据。 + +## 验证方式 +- **字段与索引**:执行 `docs/sql/10_schema/99_soft_delete_migration_v1.sql` 后,确认涉及表存在 `deleted_at/deleted_by` 字段,以及 `idx__soft_delete` 索引。 +- **RLS 过滤生效**:对任一涉及表执行查询(按当前角色权限),确认已软删的数据默认不返回。 +- **RPC 删除行为**:调用任一删除 RPC 后: + - 记录未被物理删除; + - `deleted_at` 被写入当前时间; + - `deleted_by` 被写入执行删除的管理员用户 ID; + - 对需要级联的对象,子表记录同步被标记软删。 + +## 关联文档 +- `docs/project_spec/AGENT_PROJECT_SPEC.md` +- `docs/sql/00_meta/12_soft_delete_standard.md` diff --git a/docs/sql/00_meta/11_roles_and_permissions_strategy.md b/docs/sql/00_meta/11_roles_and_permissions_strategy.md new file mode 100644 index 00000000..a439791c --- /dev/null +++ b/docs/sql/00_meta/11_roles_and_permissions_strategy.md @@ -0,0 +1,67 @@ +# 角色与权限策略(权威口径) + +> 本文档为 `docs/sql/` 权威 SQL 入库与评审的安全口径说明。任何涉及 RLS / RPC / GRANT 的变更,必须对照本文档与 `docs/project_spec/AGENT_PROJECT_SPEC.md`。 + +## 1. 权威身份字段 + +- 后台/分析端角色字段唯一权威:`public.ak_users.role` +- 推荐值:`admin` / `analytics` / `user`(以及业务需要的扩展值) + +## 2. 权限访问基本原则 + +### 2.1 最小权限原则 + +- 默认不对 `anon` / `authenticated` 开放管理侧敏感表的直接访问。 +- 管理侧的“全局读写”必须通过 RPC(函数)进行封装。 + +### 2.2 RLS 先行 + +- 所有业务表必须开启 RLS(除非明确说明原因)。 +- 对消费者端可公开读取的数据(如提货点),允许创建只读 SELECT policy,并限制条件(例如 `status = 1`)。 + +## 3. RPC 安全口径(强制) + +### 3.1 SECURITY DEFINER + +所有管理端 RPC 必须: + +- `SECURITY DEFINER` +- `SET search_path = public` +- 入口鉴权:必须校验当前用户为后台管理员 + +推荐的入口鉴权形式: + +- 通过 `public.ak_users` 校验: + - `WHERE auth_id = auth.uid() AND role = 'admin'` + +> 注:如未来引入统一函数 `get_current_user_role()`,则可改为 `get_current_user_role() IN ('admin','analytics')`。 + +### 3.2 返回字段最小化 + +- 仅返回前端所需字段。 +- 禁止返回敏感字段(如密钥、token、完整手机号/身份证号等),除非有明确业务需求与脱敏方案。 + +### 3.3 分页与限制 + +- 列表类 RPC 必须支持分页(`LIMIT/OFFSET`)或带 `LIMIT`。 + +## 4. GRANT/REVOKE 口径 + +- 默认:`REVOKE ALL ... FROM PUBLIC`。 +- 仅对需要调用 RPC 的角色授予 `EXECUTE`(通常为 `authenticated`)。 +- 禁止对 `anon/authenticated` 大范围 `GRANT` 直接表权限。 + +## 5. 入库评审要点(与 AGENT_PROJECT_SPEC.md 对齐) + +评审必须覆盖: + +- 是否开启 RLS;policy 是否过宽。 +- 是否存在不安全的 `SECURITY DEFINER`(无鉴权/未固定 search_path)。 +- 是否有破坏性语句(DROP/TRUNCATE/无 WHERE 的 UPDATE/DELETE)。 + +--- + +## 6. 关联文档 + +- `docs/project_spec/AGENT_PROJECT_SPEC.md` +- `docs/sql/00_meta/README.md` diff --git a/docs/sql/00_meta/12_soft_delete_standard.md b/docs/sql/00_meta/12_soft_delete_standard.md new file mode 100644 index 00000000..184acfcf --- /dev/null +++ b/docs/sql/00_meta/12_soft_delete_standard.md @@ -0,0 +1,75 @@ +# 数据库软删除 (Soft Delete) 统一标准规范 + +## 1. 核心目标 +- **数据保留**:防止误操作导致的数据永久丢失,支持操作审计。 +- **级联安全**:通过逻辑链路同步软删关联数据,避免孤儿数据。 +- **透明过滤**:利用 RLS(行级安全策略)或统一过滤口径,使业务层查询默认排除已标记删除的记录。 + +## 2. 字段规范 +所有需要支持软删除的业务表必须统一包含以下字段: + +| 字段名 | 类型 | 说明 | 索引建议 | +| :------------ | :------------ | :-------------------------------------- | :--------------- | +| `deleted_at` | `TIMESTAMPTZ` | 删除时间戳。非 NULL 表示已删除。 | **必须建立索引** | +| `deleted_by` | `UUID` | 执行删除操作的用户 ID (`ak_users.id`)。 | 建议索引 | +| `restored_at` | `TIMESTAMPTZ` | (可选) 最近一次恢复的时间戳。 | - | +| `restored_by` | `UUID` | (可选) 最近一次恢复的操作人。 | - | + +## 3. RLS 自动过滤口径 +为了确保查询透明性,必须在 `20_rls/` 策略中统一加入过滤条件: + +```sql +-- 示例策略:仅允许查询未删除的记录 +CREATE POLICY select_active_records ON public.your_table + FOR SELECT + TO authenticated + USING (deleted_at IS NULL); +``` + +## 4. RPC 重构准则 +删除接口必须从 `DELETE FROM ...` 改为 `UPDATE ... SET deleted_at = now()`: + +### 4.1 基础删除模板 +```sql +CREATE OR REPLACE FUNCTION public.rpc_admin_soft_delete_item(p_id UUID) +RETURNS BOOLEAN SECURITY DEFINER SET search_path = public AS $$ +BEGIN + -- 1. 鉴权 (示例) + IF NOT EXISTS ( + SELECT 1 FROM public.ak_users + WHERE auth_id = auth.uid() AND role = 'admin' + ) THEN + RAISE EXCEPTION 'Permission denied'; + END IF; + + -- 2. 执行软删 + UPDATE public.your_table + SET deleted_at = now(), deleted_by = auth.uid() + WHERE id = p_id AND deleted_at IS NULL; + + RETURN FOUND; +END; +$$ LANGUAGE plpgsql; +``` + +### 4.2 级联软删除规范 +若存在级联依赖(如:软删“商品分类”时需同步软删其下的“商品”),必须在 RPC 内部通过事务或同步 UPDATE 处理: + +```sql +-- 级联链路示例 +UPDATE public.ml_products +SET deleted_at = now(), deleted_by = auth.uid() +WHERE category_id = p_category_id AND deleted_at IS NULL; +``` + +## 5. UI 交互适配 +- **默认视图**:列表页面默认仅展示 `deleted_at IS NULL` 的数据。 +- **回收站 (可选)**:若业务需要,可提供“回收站”视图(过滤 `deleted_at IS NOT NULL`)及“恢复”功能。 +- **二次确认**:点击“删除”按钮时,必须弹出提示明确告知数据将进入逻辑删除状态。 + +--- + +## 6. 改造优先级 +1. **第一梯队**:基础资产(商品、分类、文章、分销商)。 +2. **第二梯队**:配置类(运费模板、通知模板、DIY 页面)。 +3. **第三梯队**:日志/交互类(评论、收藏、地址)。 diff --git a/docs/sql/10_schema/99_soft_delete_migration_v1.sql b/docs/sql/10_schema/99_soft_delete_migration_v1.sql new file mode 100644 index 00000000..d9281cf7 --- /dev/null +++ b/docs/sql/10_schema/99_soft_delete_migration_v1.sql @@ -0,0 +1,79 @@ +-- ===================================================================================== +-- Migration: 全量软删除 (Soft Delete) 基础设施补齐 +-- 位置:docs/sql/10_schema/99_soft_delete_migration_v1.sql +-- 对象类型:ALTER TABLE +-- 说明:为所有核心业务表补齐 deleted_at, deleted_by, restored_at, restored_by 字段 +-- 涵盖:权限、配置、内容、装修、物流、分销、财务、客服、营销、商品、用户、订单 +-- ===================================================================================== + +DO $$ +DECLARE + -- 需补齐软删除字段的业务表全量清单 + t_names TEXT[] := ARRAY[ + -- 1. 系统与权限 (Auth/System) + 'ak_roles', 'ak_permissions', 'ak_admin_roles', 'ak_role_permissions', 'ml_system_configs', + + -- 2. 内容与装修 (CMS/Decoration) + 'ml_articles', 'ml_article_categories', 'ak_diy_pages', + + -- 3. 物流资源 (Delivery) + 'ml_delivery_staff', 'ml_delivery_stations', 'ak_shipping_templates', + + -- 4. 分销体系 (Distribution) + 'ak_distribution_agents', 'ak_distribution_divisions', + 'ak_distribution_agent_applications', 'ak_distribution_division_applications', + 'ak_promoter_relations', 'ak_commission_logs', 'ak_distribution_level', 'ak_distribution_config', + + -- 5. 财务管理 (Finance) + 'ml_extract', 'ml_invoices', 'ml_user_bill', 'ml_user_recharge', + + -- 6. 客服系统 (Kefu) + 'ml_kefu_accounts', 'ml_kefu_words', 'ml_kefu_word_categories', 'ml_kefu_auto_replies', 'ml_kefu_feedbacks', + + -- 7. 营销活动 (Marketing) + 'ak_advanced_marketing', 'ak_bargain_groupbuy', 'ak_live_products', 'ak_lottery_live', + 'ak_marketing_checkin_configs', 'ak_marketing_newcomer_config', 'ak_marketing_signin_logs', + 'ak_member_management', 'ak_recharge_management', 'ak_signin_configs', + + -- 8. 商品中心 (Product) + 'ml_products', 'ml_product_skus', 'ml_categories', + 'ak_product_labels', 'ak_product_member_prices', 'ak_product_protections', 'ak_product_templates', + + -- 9. 用户管理 (User) + 'ak_user_labels', 'ak_user_groups', 'ak_user_levels', 'ak_users', + + -- 10. 订单中心 (Order) + 'ml_orders' + ]; + t_name TEXT; +BEGIN + FOREACH t_name IN ARRAY t_names LOOP + -- 检查表是否存在 + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = t_name) THEN + + -- 1. 增加 deleted_at 字段 + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t_name AND column_name = 'deleted_at') THEN + EXECUTE format('ALTER TABLE public.%I ADD COLUMN deleted_at TIMESTAMPTZ DEFAULT NULL', t_name); + END IF; + + -- 2. 增加 deleted_by 字段 + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t_name AND column_name = 'deleted_by') THEN + EXECUTE format('ALTER TABLE public.%I ADD COLUMN deleted_by UUID REFERENCES public.ak_users(id) ON DELETE SET NULL', t_name); + END IF; + + -- 3. 增加 restored_at 字段 + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t_name AND column_name = 'restored_at') THEN + EXECUTE format('ALTER TABLE public.%I ADD COLUMN restored_at TIMESTAMPTZ DEFAULT NULL', t_name); + END IF; + + -- 4. 增加 restored_by 字段 + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = t_name AND column_name = 'restored_by') THEN + EXECUTE format('ALTER TABLE public.%I ADD COLUMN restored_by UUID REFERENCES public.ak_users(id) ON DELETE SET NULL', t_name); + END IF; + + -- 5. 建立软删除索引 + EXECUTE format('CREATE INDEX IF NOT EXISTS %I ON public.%I (deleted_at) WHERE deleted_at IS NULL', 'idx_' || t_name || '_soft_delete', t_name); + + END IF; + END LOOP; +END $$; diff --git a/docs/sql/10_schema/auth/ak_auth_security_constraints_v1.sql b/docs/sql/10_schema/auth/ak_auth_security_constraints_v1.sql new file mode 100644 index 00000000..7185fadb --- /dev/null +++ b/docs/sql/10_schema/auth/ak_auth_security_constraints_v1.sql @@ -0,0 +1,84 @@ +-- ===================================================================================== +-- Migration: Auth 安全约束增强 +-- 位置:docs/sql/10_schema/auth/ak_auth_security_constraints_v1.sql +-- 对象类型:ALTER TABLE / CONSTRAINT +-- 说明:增强 ak_users 与 auth.users 的关联安全性,防止孤儿数据 +-- ===================================================================================== + +-- 1. 确保 ak_users.auth_id 存在外键约束指向 auth.users +-- 注意:Supabase 的 auth.users 表在 auth schema 下,需要确保权限正确 +DO $$ +BEGIN + -- 检查是否已存在外键约束 + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_ak_users_auth_id' + AND table_name = 'ak_users' + ) THEN + -- 添加外键约束,当 auth.users 被删除时自动删除对应的 profile + ALTER TABLE public.ak_users + ADD CONSTRAINT fk_ak_users_auth_id + FOREIGN KEY (auth_id) REFERENCES auth.users(id) + ON DELETE CASCADE; + END IF; +END $$; + +-- 2. 为 auth_id 建立唯一索引,确保一个 auth 用户只有一个 profile +CREATE UNIQUE INDEX IF NOT EXISTS idx_ak_users_auth_id_unique +ON public.ak_users(auth_id); + +-- 3. 为 role 字段建立索引,加速权限查询 +CREATE INDEX IF NOT EXISTS idx_ak_users_role +ON public.ak_users(role); + +-- 4. 添加检查约束,确保 role 字段只能是有效值 +ALTER TABLE public.ak_users +DROP CONSTRAINT IF EXISTS chk_ak_users_role_valid; + +ALTER TABLE public.ak_users +ADD CONSTRAINT chk_ak_users_role_valid +CHECK (role IN ('user', 'admin', 'staff', 'agent', 'kefu') OR role IS NULL); + +-- 5. 为 ak_admin_roles 添加约束确保关联有效性 +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_ak_admin_roles_admin_id' + AND table_name = 'ak_admin_roles' + ) THEN + ALTER TABLE public.ak_admin_roles + ADD CONSTRAINT fk_ak_admin_roles_admin_id + FOREIGN KEY (admin_id) REFERENCES public.ak_users(id) + ON DELETE CASCADE; + END IF; +END $$; + +-- 6. 为 ak_role_permissions 添加约束确保关联有效性 +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_ak_role_permissions_role_id' + AND table_name = 'ak_role_permissions' + ) THEN + ALTER TABLE public.ak_role_permissions + ADD CONSTRAINT fk_ak_role_permissions_role_id + FOREIGN KEY (role_id) REFERENCES public.ak_roles(id) + ON DELETE CASCADE; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE constraint_name = 'fk_ak_role_permissions_permission_id' + AND table_name = 'ak_role_permissions' + ) THEN + ALTER TABLE public.ak_role_permissions + ADD CONSTRAINT fk_ak_role_permissions_permission_id + FOREIGN KEY (permission_id) REFERENCES public.ak_permissions(id) + ON DELETE CASCADE; + END IF; +END $$; \ No newline at end of file diff --git a/docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql b/docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql index 0d9a5656..340c9604 100644 --- a/docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql +++ b/docs/sql/20_rls/admin/ml_system_configs_rls_v1.sql @@ -12,6 +12,6 @@ ALTER TABLE public.ml_system_configs ENABLE ROW LEVEL SECURITY; -- 1. 允许所有登录用户读取配置 (用于前端业务逻辑判断) DROP POLICY IF EXISTS system_configs_select_policy ON public.ml_system_configs; CREATE POLICY system_configs_select_policy ON public.ml_system_configs -FOR SELECT TO authenticated USING (true); +FOR SELECT TO authenticated USING (deleted_at IS NULL); -- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行,此处不再额外开放直接表操作 diff --git a/docs/sql/20_rls/cms/ml_cms_rls_v1.sql b/docs/sql/20_rls/cms/ml_cms_rls_v1.sql index e7b18723..b598edb5 100644 --- a/docs/sql/20_rls/cms/ml_cms_rls_v1.sql +++ b/docs/sql/20_rls/cms/ml_cms_rls_v1.sql @@ -16,7 +16,7 @@ CREATE POLICY ml_article_categories_select_active ON public.ml_article_categories FOR SELECT TO anon, authenticated - USING (status = 1); + USING (status = 1 AND deleted_at IS NULL); -- 3. 文章表策略:允许所有人读取已发布的文章 DROP POLICY IF EXISTS ml_articles_select_published ON public.ml_articles; @@ -24,6 +24,6 @@ CREATE POLICY ml_articles_select_published ON public.ml_articles FOR SELECT TO anon, authenticated - USING (status = 1); + USING (status = 1 AND deleted_at IS NULL); -- 默认不开放 INSERT/UPDATE/DELETE 给普通用户,管理端操作通过 RPC (SECURITY DEFINER) 执行 diff --git a/docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql b/docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql index 1c6ac274..e8ab8c9c 100644 --- a/docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql +++ b/docs/sql/20_rls/decoration/ml_decoration_rls_v1.sql @@ -13,6 +13,6 @@ ALTER TABLE public.ak_diy_pages ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS diy_pages_select_active ON public.ak_diy_pages; CREATE POLICY diy_pages_select_active ON public.ak_diy_pages FOR SELECT TO anon, authenticated -USING (is_active = true); +USING (is_active = true AND deleted_at IS NULL); -- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行,此处不再额外开放直接表操作 diff --git a/docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql b/docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql index 342d524b..c100a680 100644 --- a/docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql +++ b/docs/sql/20_rls/delivery/ak_delivery_rls_v1.sql @@ -19,6 +19,6 @@ CREATE POLICY delivery_stations_select_active ON public.ml_delivery_stations FOR SELECT TO anon, authenticated - USING (status = 1); + USING (status = 1 AND deleted_at IS NULL); -- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行 diff --git a/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql b/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql index 32722f20..b451394f 100644 --- a/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql +++ b/docs/sql/20_rls/distribution/ml_distribution_rls_v1.sql @@ -18,35 +18,35 @@ ALTER TABLE public.ak_distribution_agent_applications ENABLE ROW LEVEL SECURITY; -- 1. 分销配置:允许所有登录用户读取(消费者端展示逻辑需要) DROP POLICY IF EXISTS dist_config_select_policy ON public.ak_distribution_config; CREATE POLICY dist_config_select_policy ON public.ak_distribution_config -FOR SELECT TO authenticated USING (true); +FOR SELECT TO authenticated USING (deleted_at IS NULL); -- 2. 分销等级:允许所有登录用户读取可见等级 DROP POLICY IF EXISTS dist_level_select_policy ON public.ak_distribution_level; CREATE POLICY dist_level_select_policy ON public.ak_distribution_level -FOR SELECT TO authenticated USING (is_visible = true); +FOR SELECT TO authenticated USING (is_visible = true AND deleted_at IS NULL); -- 3. 推广员关系:用户仅能查看与自己相关的记录 DROP POLICY IF EXISTS promoter_relations_select_policy ON public.ak_promoter_relations; CREATE POLICY promoter_relations_select_policy ON public.ak_promoter_relations -FOR SELECT TO authenticated USING (uid = auth.uid() OR inviter_uid = auth.uid()); +FOR SELECT TO authenticated USING ((uid = auth.uid() OR inviter_uid = auth.uid()) AND deleted_at IS NULL); -- 4. 佣金日志:用户仅能查看自己的佣金记录 DROP POLICY IF EXISTS commission_logs_select_policy ON public.ak_commission_logs; CREATE POLICY commission_logs_select_policy ON public.ak_commission_logs -FOR SELECT TO authenticated USING (uid = auth.uid()); +FOR SELECT TO authenticated USING (uid = auth.uid() AND deleted_at IS NULL); -- 5. 事业部与代理商:允许登录用户查看启用的记录 DROP POLICY IF EXISTS dist_divisions_select_policy ON public.ak_distribution_divisions; CREATE POLICY dist_divisions_select_policy ON public.ak_distribution_divisions -FOR SELECT TO authenticated USING (is_enabled = true); +FOR SELECT TO authenticated USING (is_enabled = true AND deleted_at IS NULL); DROP POLICY IF EXISTS dist_agents_select_policy ON public.ak_distribution_agents; CREATE POLICY dist_agents_select_policy ON public.ak_distribution_agents -FOR SELECT TO authenticated USING (is_enabled = true); +FOR SELECT TO authenticated USING (is_enabled = true AND deleted_at IS NULL); -- 6. 代理商申请:用户仅能管理自己的申请记录 DROP POLICY IF EXISTS dist_apply_user_policy ON public.ak_distribution_agent_applications; CREATE POLICY dist_apply_user_policy ON public.ak_distribution_agent_applications -FOR ALL TO authenticated USING (uid = auth.uid()) WITH CHECK (uid = auth.uid()); +FOR ALL TO authenticated USING (uid = auth.uid() AND deleted_at IS NULL) WITH CHECK (uid = auth.uid()); -- 管理端全量管理将通过 SECURITY DEFINER 的 RPC 接口执行,此处不再额外开放直接表操作 diff --git a/docs/sql/20_rls/finance/ml_extract_rls_v1.sql b/docs/sql/20_rls/finance/ml_extract_rls_v1.sql index 7831493f..542cc855 100644 --- a/docs/sql/20_rls/finance/ml_extract_rls_v1.sql +++ b/docs/sql/20_rls/finance/ml_extract_rls_v1.sql @@ -14,7 +14,7 @@ CREATE POLICY ml_extract_user_select ON public.ml_extract FOR SELECT TO authenticated - USING (uid = auth.uid()); + USING (uid = auth.uid() AND deleted_at IS NULL); -- 默认不开放 INSERT/UPDATE/DELETE 给普通用户 -- 提现申请通常由特定的 RPC 函数 (security definer) 创建,以确保业务逻辑(如冻结余额)的原子性 diff --git a/docs/sql/20_rls/finance/ml_invoices_rls_v1.sql b/docs/sql/20_rls/finance/ml_invoices_rls_v1.sql index e2418cf0..de4f8cb1 100644 --- a/docs/sql/20_rls/finance/ml_invoices_rls_v1.sql +++ b/docs/sql/20_rls/finance/ml_invoices_rls_v1.sql @@ -8,12 +8,12 @@ ALTER TABLE public.ml_invoices ENABLE ROW LEVEL SECURITY; --- 策略 1: 允许用户读取自己的记录 +-- 策略 1: 允许用户读取自己的记录(仅未删除数据) DROP POLICY IF EXISTS ml_invoices_user_select ON public.ml_invoices; CREATE POLICY ml_invoices_user_select ON public.ml_invoices FOR SELECT TO authenticated - USING (uid = auth.uid()); + USING (uid = auth.uid() AND deleted_at IS NULL); -- 默认不开放 INSERT/UPDATE/DELETE 给普通用户,通常由 RPC 或支付后逻辑触发 diff --git a/docs/sql/20_rls/finance/ml_user_bill_rls_v1.sql b/docs/sql/20_rls/finance/ml_user_bill_rls_v1.sql index c629fec9..4e0cd683 100644 --- a/docs/sql/20_rls/finance/ml_user_bill_rls_v1.sql +++ b/docs/sql/20_rls/finance/ml_user_bill_rls_v1.sql @@ -8,12 +8,12 @@ ALTER TABLE public.ml_user_bill ENABLE ROW LEVEL SECURITY; --- 策略 1: 允许用户读取自己的记录 +-- 策略 1: 允许用户读取自己的记录(仅未删除数据) DROP POLICY IF EXISTS ml_user_bill_user_select ON public.ml_user_bill; CREATE POLICY ml_user_bill_user_select ON public.ml_user_bill FOR SELECT TO authenticated - USING (uid = auth.uid()); + USING (uid = auth.uid() AND deleted_at IS NULL); -- 默认不开放 INSERT/UPDATE/DELETE 给普通用户,由后端逻辑或 RPC 触发 diff --git a/docs/sql/20_rls/finance/ml_user_recharge_rls_v1.sql b/docs/sql/20_rls/finance/ml_user_recharge_rls_v1.sql index 6ba7eaa9..bcbd6ba0 100644 --- a/docs/sql/20_rls/finance/ml_user_recharge_rls_v1.sql +++ b/docs/sql/20_rls/finance/ml_user_recharge_rls_v1.sql @@ -8,12 +8,12 @@ ALTER TABLE public.ml_user_recharge ENABLE ROW LEVEL SECURITY; --- 策略 1: 允许用户读取自己的记录 +-- 策略 1: 允许用户读取自己的记录(仅未删除数据) DROP POLICY IF EXISTS ml_user_recharge_user_select ON public.ml_user_recharge; CREATE POLICY ml_user_recharge_user_select ON public.ml_user_recharge FOR SELECT TO authenticated - USING (uid = auth.uid()); + USING (uid = auth.uid() AND deleted_at IS NULL); -- 默认不开放 INSERT/UPDATE/DELETE 给普通用户,写操作通常由业务逻辑或支付回调触发 diff --git a/docs/sql/30_rpc/auth/fn_check_admin_permission_v1.sql b/docs/sql/30_rpc/auth/fn_check_admin_permission_v1.sql new file mode 100644 index 00000000..b92ab759 --- /dev/null +++ b/docs/sql/30_rpc/auth/fn_check_admin_permission_v1.sql @@ -0,0 +1,55 @@ +-- ===================================================================================== +-- 函数: check_admin_permission +-- 描述: 通用的 RBAC 权限校验函数 +-- 参数: p_permission_code - 权限编码 (如 'role:delete', 'user:view') +-- 返回: BOOLEAN +-- ===================================================================================== + +CREATE OR REPLACE FUNCTION public.check_admin_permission( + p_permission_code TEXT DEFAULT NULL +) +RETURNS BOOLEAN +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_user_id UUID; + v_role TEXT; +BEGIN + -- 1. 获取当前登录用户的 Profile ID 和角色 + SELECT id, role INTO v_user_id, v_role + FROM public.ak_users + WHERE auth_id = auth.uid(); + + -- 2. 未登录或未找到 Profile + IF v_user_id IS NULL THEN + RETURN FALSE; + END IF; + + -- 3. 超级管理员拥有所有权限 (保持向下兼容) + IF v_role = 'admin' THEN + RETURN TRUE; + END IF; + + -- 4. 如果指定了权限编码,则检查 ak_permissions 体系 + IF p_permission_code IS NOT NULL THEN + RETURN EXISTS ( + SELECT 1 + FROM public.ak_admin_roles ar + JOIN public.ak_role_permissions rp ON ar.role_id = rp.role_id + JOIN public.ak_permissions p ON rp.permission_id = p.id + WHERE ar.admin_id = v_user_id + AND p.code = p_permission_code + AND p.deleted_at IS NULL + AND ar.deleted_at IS NULL + ); + END IF; + + RETURN FALSE; +END; +$$; + +-- 授权 +REVOKE ALL ON FUNCTION public.check_admin_permission(TEXT) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION public.check_admin_permission(TEXT) TO authenticated; diff --git a/docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql b/docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql index cc6b3981..93e7343b 100644 --- a/docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql +++ b/docs/sql/30_rpc/auth/rpc_admin_delete_permission_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_permission --- 管理端删除功能权限/菜单 +-- 管理端删除功能权限/菜单(支持级联软删除关联的角色权限映射) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_permission( p_id UUID @@ -11,17 +11,29 @@ SET search_path = public AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN - -- 1. 权限检查 (仅管理员) - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role = 'admin' - ) THEN - RAISE EXCEPTION 'Permission denied'; + -- 1. 权限检查 (使用通用权限校验函数,权限编码: permission:delete) + IF NOT public.check_admin_permission('permission:delete') THEN + RAISE EXCEPTION 'Permission denied: permission:delete'; END IF; - -- 2. 执行级联删除 (外键已配置 ON DELETE CASCADE) - DELETE FROM public.ak_permissions WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id + FROM public.ak_users + WHERE auth_id = auth.uid(); + + -- 3. 级联软删除:先删除所有关联了该权限的角色映射 + UPDATE public.ak_role_permissions + SET deleted_at = now(), + deleted_by = v_user_id + WHERE permission_id = p_id AND deleted_at IS NULL; + + -- 4. 最后软删除权限本身 + UPDATE public.ak_permissions + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql b/docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql index 3bb7706e..4a2f2c49 100644 --- a/docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql +++ b/docs/sql/30_rpc/auth/rpc_admin_delete_role_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_role --- 管理端删除角色 +-- 管理端删除角色(支持级联软删除关联权限) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_role( p_id UUID @@ -11,17 +11,35 @@ SET search_path = public AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN - -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role = 'admin' - ) THEN - RAISE EXCEPTION 'Permission denied'; + -- 1. 权限检查 (使用通用权限校验函数,权限编码: role:delete) + IF NOT public.check_admin_permission('role:delete') THEN + RAISE EXCEPTION 'Permission denied: role:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ak_roles WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id + FROM public.ak_users + WHERE auth_id = auth.uid(); + + -- 3. 级联软删除:先删除该角色下的所有权限关联 + UPDATE public.ak_role_permissions + SET deleted_at = now(), + deleted_by = v_user_id + WHERE role_id = p_id AND deleted_at IS NULL; + + -- 4. 级联软删除:再删除该角色下的所有管理员关联 + UPDATE public.ak_admin_roles + SET deleted_at = now(), + deleted_by = v_user_id + WHERE role_id = p_id AND deleted_at IS NULL; + + -- 5. 最后软删除角色本身 + UPDATE public.ak_roles + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql b/docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql index e08fc218..442c3310 100644 --- a/docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql +++ b/docs/sql/30_rpc/auth/rpc_admin_get_permission_list_v1.sql @@ -26,6 +26,7 @@ BEGIN path, icon, sort_order, is_visible, created_at, updated_at FROM public.ak_permissions + WHERE deleted_at IS NULL ORDER BY sort_order ASC, created_at ASC ) t; diff --git a/docs/sql/30_rpc/cms/rpc_admin_article_category_delete_v1.sql b/docs/sql/30_rpc/cms/rpc_admin_article_category_delete_v1.sql index 4e4bdf6e..db050cad 100644 --- a/docs/sql/30_rpc/cms/rpc_admin_article_category_delete_v1.sql +++ b/docs/sql/30_rpc/cms/rpc_admin_article_category_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/cms/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除文章分类(需检查是否有关联文章) +-- 说明:管理端删除文章分类(支持级联软删除分类下的文章) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_article_category_delete( @@ -15,33 +15,32 @@ SET search_path = public LANGUAGE plpgsql AS $$ DECLARE - v_has_articles BOOLEAN; v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('cms:category:delete') THEN + RAISE EXCEPTION 'Permission denied: cms:category:delete'; END IF; - -- 2. 检查是否有关联文章 - SELECT EXISTS ( - SELECT 1 FROM public.ml_articles - WHERE category_id = p_id - ) INTO v_has_articles; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); - IF v_has_articles THEN - RAISE EXCEPTION 'Cannot delete category with associated articles'; - END IF; + -- 3. 级联软删除:该分类下的所有文章 + UPDATE public.ml_articles + SET deleted_at = now(), + deleted_by = v_user_id + WHERE category_id = p_id AND deleted_at IS NULL; - -- 3. 执行物理删除 - DELETE FROM public.ml_article_categories WHERE id = p_id; + -- 4. 软删除分类本身 + UPDATE public.ml_article_categories + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; END; $$; -COMMENT ON FUNCTION public.rpc_admin_article_category_delete IS '管理员删除文章分类(含关联性检查)'; +COMMENT ON FUNCTION public.rpc_admin_article_category_delete IS '管理员删除文章分类(级联软删除关联文章)'; diff --git a/docs/sql/30_rpc/cms/rpc_admin_article_delete_v1.sql b/docs/sql/30_rpc/cms/rpc_admin_article_delete_v1.sql index c14e693e..41c09773 100644 --- a/docs/sql/30_rpc/cms/rpc_admin_article_delete_v1.sql +++ b/docs/sql/30_rpc/cms/rpc_admin_article_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/cms/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除文章记录 +-- 说明:管理端删除文章记录(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_article_delete( @@ -16,17 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('cms:article:delete') THEN + RAISE EXCEPTION 'Permission denied: cms:article:delete'; END IF; - -- 2. 执行物理删除 - DELETE FROM public.ml_articles WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 执行软删除 + UPDATE public.ml_articles + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql b/docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql index f40d2d58..ef8d1101 100644 --- a/docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql +++ b/docs/sql/30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_diy_page --- 管理端删除 DIY 页面配置 +-- 管理端删除 DIY 页面配置(支持权限检查与首页保护) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_diy_page( p_id uuid @@ -11,24 +11,29 @@ SET search_path = public AS $$ DECLARE v_ok boolean; + v_user_id UUID; BEGIN - -- 1. 权限检查 (仅管理员) - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users u - WHERE u.id = auth.uid() AND u.role = 'admin' - ) THEN - RAISE EXCEPTION 'permission denied'; + -- 1. 权限检查 (使用通用权限校验函数) + IF NOT public.check_admin_permission('decoration:page:delete') THEN + RAISE EXCEPTION 'Permission denied: decoration:page:delete'; END IF; - -- 2. 执行删除 (不允许删除当前生效的首页) + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 校验:不允许删除当前生效的首页 IF EXISTS ( SELECT 1 FROM public.ak_diy_pages - WHERE id = p_id AND is_home = true + WHERE id = p_id AND is_home = true AND deleted_at IS NULL ) THEN RAISE EXCEPTION 'cannot delete the active home page'; END IF; - DELETE FROM public.ak_diy_pages WHERE id = p_id; + -- 4. 执行软删除:标记 deleted_at + UPDATE public.ak_diy_pages + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql b/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql index fe1d4e09..429e21d2 100644 --- a/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql +++ b/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_delivery_staff --- 管理端删除配送员 +-- 管理端删除配送员(支持权限检查) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_delivery_staff( p_id UUID @@ -11,17 +11,21 @@ SET search_path = public AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN - -- 1. 权限检查 (仅管理员) - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role = 'admin' - ) THEN - RAISE EXCEPTION 'Permission denied'; + -- 1. 权限检查 (使用通用权限校验函数) + IF NOT public.check_admin_permission('delivery:staff:delete') THEN + RAISE EXCEPTION 'Permission denied: delivery:staff:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ml_delivery_staff WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 执行软删除:标记 deleted_at + UPDATE public.ml_delivery_staff + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql b/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql index 82d067b7..60ac3c46 100644 --- a/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql +++ b/docs/sql/30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_delivery_station --- 管理端删除提货点/核销点 +-- 管理端删除提货点/核销点(支持级联软删除配送员关联) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_delivery_station( p_id UUID @@ -11,17 +11,27 @@ SET search_path = public AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN - -- 1. 权限检查 (仅管理员) - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role = 'admin' - ) THEN - RAISE EXCEPTION 'Permission denied'; + -- 1. 权限检查 + IF NOT public.check_admin_permission('delivery:station:delete') THEN + RAISE EXCEPTION 'Permission denied: delivery:station:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ml_delivery_stations WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 级联软删除:该站点下的所有配送员 + UPDATE public.ml_delivery_staff + SET deleted_at = now(), + deleted_by = v_user_id + WHERE station_id = p_id AND deleted_at IS NULL; + + -- 4. 执行软删除站点本身 + UPDATE public.ml_delivery_stations + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql index 13d5018b..495b270a 100644 --- a/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql +++ b/docs/sql/30_rpc/distribution/rpc_admin_delete_agent_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_agent --- 管理端删除代理商 +-- 管理端删除代理商(支持级联软删除代理申请记录) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_agent( p_uid uuid @@ -11,16 +11,27 @@ SET search_path = public AS $$ DECLARE v_ok boolean; + v_user_id UUID; BEGIN - -- 仅管理员可操作 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users u - WHERE u.id = auth.uid() AND u.role = 'admin' - ) THEN - RAISE EXCEPTION 'permission denied'; + -- 1. 权限检查 + IF NOT public.check_admin_permission('distribution:agent:delete') THEN + RAISE EXCEPTION 'Permission denied: distribution:agent:delete'; END IF; - DELETE FROM public.ak_distribution_agents WHERE uid = p_uid; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 级联软删除:该代理的所有申请记录 + UPDATE public.ak_distribution_agent_applications + SET deleted_at = now(), + deleted_by = v_user_id + WHERE user_id = p_uid AND deleted_at IS NULL; + + -- 4. 软删除代理商记录 + UPDATE public.ak_distribution_agents + SET deleted_at = now(), + deleted_by = v_user_id + WHERE uid = p_uid AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql b/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql index c7781003..e27fa621 100644 --- a/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql +++ b/docs/sql/30_rpc/distribution/rpc_admin_delete_division_v1.sql @@ -1,5 +1,5 @@ -- RPC: rpc_admin_delete_division --- 管理端删除事业部 +-- 管理端删除事业部(支持级联软删除关联代理) CREATE OR REPLACE FUNCTION public.rpc_admin_delete_division( p_uid uuid @@ -11,23 +11,33 @@ SET search_path = public AS $$ DECLARE v_ok boolean; + v_user_id UUID; BEGIN - -- 仅管理员可操作 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users u - WHERE u.id = auth.uid() AND u.role = 'admin' - ) THEN - RAISE EXCEPTION 'permission denied'; + -- 1. 权限检查 + IF NOT public.check_admin_permission('distribution:division:delete') THEN + RAISE EXCEPTION 'Permission denied: distribution:division:delete'; END IF; - -- 检查是否有关联代理商 - IF EXISTS ( - SELECT 1 FROM public.ak_distribution_agents WHERE division_uid = p_uid - ) THEN - RAISE EXCEPTION 'cannot delete division with associated agents'; - END IF; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); - DELETE FROM public.ak_distribution_divisions WHERE uid = p_uid; + -- 3. 级联软删除:该事业部下的所有代理商 + UPDATE public.ak_distribution_agents + SET deleted_at = now(), + deleted_by = v_user_id + WHERE division_uid = p_uid AND deleted_at IS NULL; + + -- 4. 级联软删除:该事业部的所有申请记录 + UPDATE public.ak_distribution_division_applications + SET deleted_at = now(), + deleted_by = v_user_id + WHERE user_id = p_uid AND deleted_at IS NULL; + + -- 5. 软删除事业部本身 + UPDATE public.ak_distribution_divisions + SET deleted_at = now(), + deleted_by = v_user_id + WHERE uid = p_uid AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql b/docs/sql/30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql index 92a97aee..617c73e9 100644 --- a/docs/sql/30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql +++ b/docs/sql/30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/kefu/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除客服账号 +-- 说明:管理端删除客服账号(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_kefu_account_delete( @@ -16,17 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('kefu:account:delete') THEN + RAISE EXCEPTION 'Permission denied: kefu:account:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ml_kefu_accounts WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 执行软删除:标记 deleted_at + UPDATE public.ml_kefu_accounts + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql b/docs/sql/30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql index ffc84f28..1e3a16d5 100644 --- a/docs/sql/30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql +++ b/docs/sql/30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/kefu/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除客服自动回复配置 +-- 说明:管理端删除客服自动回复配置(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_kefu_auto_reply_delete( @@ -16,17 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('kefu:auto_reply:delete') THEN + RAISE EXCEPTION 'Permission denied: kefu:auto_reply:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ml_kefu_auto_replies WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 执行软删除:标记 deleted_at + UPDATE public.ml_kefu_auto_replies + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql b/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql index 4e535331..dd814e7f 100644 --- a/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql +++ b/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/kefu/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除话术分类 +-- 说明:管理端删除话术分类(支持级联软删除话术) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_kefu_word_category_delete( @@ -16,17 +16,27 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('kefu:word:category:delete') THEN + RAISE EXCEPTION 'Permission denied: kefu:word:category:delete'; END IF; - -- 2. 执行删除 (ml_kefu_words 已设置 ON DELETE CASCADE) - DELETE FROM public.ml_kefu_word_categories WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 级联软删除:先标记该分类下的话术为删除 + UPDATE public.ml_kefu_words + SET deleted_at = now(), + deleted_by = v_user_id + WHERE category_id = p_id AND deleted_at IS NULL; + + -- 4. 执行软删除分类本身:标记 deleted_at + UPDATE public.ml_kefu_word_categories + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql b/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql index b3134f8d..36b94d72 100644 --- a/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql +++ b/docs/sql/30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/kefu/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:管理端删除快捷话术 +-- 说明:管理端删除快捷话术(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_kefu_word_delete( @@ -16,17 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('kefu:word:delete') THEN + RAISE EXCEPTION 'Permission denied: kefu:word:delete'; END IF; - -- 2. 执行删除 - DELETE FROM public.ml_kefu_words WHERE id = p_id; + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 执行软删除:标记 deleted_at + UPDATE public.ml_kefu_words + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; RETURN v_ok; diff --git a/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql b/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql index 7b3cfc9b..023d6081 100644 --- a/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql +++ b/docs/sql/30_rpc/product/rpc_admin_category_delete_v1.sql @@ -2,9 +2,8 @@ -- Admin 商品模块 - 删除分类 RPC -- 位置:docs/sql/30_rpc/product/ -- 对象类型:RPC 函数(SECURITY DEFINER) --- 方案:方案 1(有子项禁止删除) --- 版本:v1 --- 依赖:ml_categories, ak_users 表已存在 +-- 版本:v1(支持级联软删除商品关联) +-- 依赖:ml_categories, ml_products, ak_users 表已存在 -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_category_delete( @@ -15,31 +14,36 @@ SECURITY DEFINER SET search_path = public LANGUAGE plpgsql AS $$ +DECLARE + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('product:category:delete') THEN + RAISE EXCEPTION 'Permission denied: product:category:delete'; END IF; - -- 2. 检查是否有子分类 (方案 1) + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 检查是否有子分类 (方案 1) IF EXISTS ( - SELECT 1 FROM public.ml_categories WHERE parent_id = p_id + SELECT 1 FROM public.ml_categories + WHERE parent_id = p_id AND deleted_at IS NULL ) THEN RAISE EXCEPTION '请先删除该分类下的子分类'; END IF; - -- 3. 检查是否有商品关联 (可选,通常作为安全保障) - IF EXISTS ( - SELECT 1 FROM public.ml_products WHERE category_id = p_id AND status != 4 - ) THEN - RAISE EXCEPTION '该分类下仍有商品,无法删除'; - END IF; + -- 4. 级联软删除:该分类下的所有商品 + UPDATE public.ml_products + SET deleted_at = now(), + deleted_by = v_user_id + WHERE category_id = p_id AND deleted_at IS NULL; - -- 4. 执行删除 - DELETE FROM public.ml_categories WHERE id = p_id; + -- 5. 执行软删除分类本身 + UPDATE public.ml_categories + SET deleted_at = now(), + deleted_by = v_user_id + WHERE id = p_id AND deleted_at IS NULL; RETURN FOUND; END; diff --git a/docs/sql/30_rpc/user/rpc_admin_user_group_delete_v1.sql b/docs/sql/30_rpc/user/rpc_admin_user_group_delete_v1.sql index 5fd60697..7e2e1b7e 100644 --- a/docs/sql/30_rpc/user/rpc_admin_user_group_delete_v1.sql +++ b/docs/sql/30_rpc/user/rpc_admin_user_group_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/user/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:逻辑删除用户分组(设置 deleted_at) +-- 说明:逻辑删除用户分组(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_user_group_delete( @@ -16,18 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('user:group:delete') THEN + RAISE EXCEPTION 'Permission denied: user:group:delete'; END IF; - -- 2. 逻辑删除 + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 逻辑删除 UPDATE public.ak_user_groups - SET deleted_at = now(), updated_at = now() + SET deleted_at = now(), + updated_at = now(), + deleted_by = v_user_id WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; diff --git a/docs/sql/30_rpc/user/rpc_admin_user_label_delete_v1.sql b/docs/sql/30_rpc/user/rpc_admin_user_label_delete_v1.sql index 04b397cf..839e72c6 100644 --- a/docs/sql/30_rpc/user/rpc_admin_user_label_delete_v1.sql +++ b/docs/sql/30_rpc/user/rpc_admin_user_label_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/user/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:逻辑删除用户标签(设置 deleted_at) +-- 说明:逻辑删除用户标签(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_user_label_delete( @@ -16,18 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('user:label:delete') THEN + RAISE EXCEPTION 'Permission denied: user:label:delete'; END IF; - -- 2. 逻辑删除 + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 逻辑删除 UPDATE public.ak_user_labels - SET deleted_at = now(), updated_at = now() + SET deleted_at = now(), + updated_at = now(), + deleted_by = v_user_id WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; diff --git a/docs/sql/30_rpc/user/rpc_admin_user_level_delete_v1.sql b/docs/sql/30_rpc/user/rpc_admin_user_level_delete_v1.sql index aed3d544..36e3fde1 100644 --- a/docs/sql/30_rpc/user/rpc_admin_user_level_delete_v1.sql +++ b/docs/sql/30_rpc/user/rpc_admin_user_level_delete_v1.sql @@ -3,7 +3,7 @@ -- 位置:docs/sql/30_rpc/user/ -- 对象类型:RPC 函数 (SECURITY DEFINER) -- 版本:v1 --- 说明:逻辑删除用户等级(设置 deleted_at) +-- 说明:逻辑删除用户等级(使用通用权限校验) -- ===================================================================================== CREATE OR REPLACE FUNCTION public.rpc_admin_user_level_delete( @@ -16,18 +16,21 @@ LANGUAGE plpgsql AS $$ DECLARE v_ok BOOLEAN; + v_user_id UUID; BEGIN -- 1. 权限检查 - IF NOT EXISTS ( - SELECT 1 FROM public.ak_users - WHERE auth_id = auth.uid() AND role IN ('admin', 'analytics') - ) THEN - RAISE EXCEPTION 'Permission denied'; + IF NOT public.check_admin_permission('user:level:delete') THEN + RAISE EXCEPTION 'Permission denied: user:level:delete'; END IF; - -- 2. 逻辑删除 + -- 2. 获取当前操作用户 ID + SELECT id INTO v_user_id FROM public.ak_users WHERE auth_id = auth.uid(); + + -- 3. 逻辑删除 UPDATE public.ak_user_levels - SET deleted_at = now(), updated_at = now() + SET deleted_at = now(), + updated_at = now(), + deleted_by = v_user_id WHERE id = p_id AND deleted_at IS NULL; GET DIAGNOSTICS v_ok = ROW_COUNT; diff --git a/docs/sql/README_EXECUTION_ORDER.md b/docs/sql/README_EXECUTION_ORDER.md new file mode 100644 index 00000000..80be0895 --- /dev/null +++ b/docs/sql/README_EXECUTION_ORDER.md @@ -0,0 +1,489 @@ +# SQL 文件执行顺序文档(软删除标准化改造 + Admin 模块全量) + +## 目标 +为本次“软删除标准化改造(Schema + RLS + RPC)”提供**可直接执行**且**顺序准确**的数据库同步步骤,同时整理**全量 Admin 模块**对应的数据库文件,确保: +- 先补齐表字段与索引(避免 RPC/RLS 引用字段失败); +- 再应用 RLS(确保查询默认过滤已软删数据); +- 最后加载/替换 RPC(确保删除行为变更与审计字段写入生效)。 + +## 前置约束 +- 执行账号需具备:`ALTER TABLE`、`CREATE INDEX`、`ALTER POLICY/CREATE POLICY`、`CREATE OR REPLACE FUNCTION` 等权限。 +- **禁止并发执行**:建议串行执行,尤其是 RLS/RPC 部分。 +- 若生产环境已存在同名对象:本次以 `CREATE OR REPLACE FUNCTION` 与 policy 更新为主,属于“覆盖式更新”。 + +--- + +## 第一阶段:Schema / Migration(必须先执行) +> 目的:补齐 `deleted_at/deleted_by/restored_at/restored_by` 字段与软删除索引,作为后续 RLS/RPC 的基础依赖。 + +| 序号 | 文件路径 | 说明 | +|:--:|:---|:---| +| 1 | [`10_schema/99_soft_delete_migration_v1.sql`](./10_schema/99_soft_delete_migration_v1.sql) | 软删除字段迁移(核心) | + +### 其他 Schema 文件(按业务域分类) + +#### Admin 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/admin/ml_system_configs_v1.sql`](./10_schema/admin/ml_system_configs_v1.sql) | 系统配置表 | + +#### Auth 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/user/ak_auth_system_v1.sql`](./10_schema/user/ak_auth_system_v1.sql) | 认证系统表(roles/permissions/admin_roles/role_permissions) | +| [`10_schema/user/ak_users_add_phone_real_name_v1.sql`](./10_schema/user/ak_users_add_phone_real_name_v1.sql) | 用户扩展字段 | +| [`10_schema/user/ak_users_finance_fields_v1.sql`](./10_schema/user/ak_users_finance_fields_v1.sql) | 用户财务字段 | +| [`10_schema/user/ak_users_constraints_fix_v1.sql`](./10_schema/user/ak_users_constraints_fix_v1.sql) | 用户表约束修复 | + +#### CMS 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/cms/ml_cms_tables_v1.sql`](./10_schema/cms/ml_cms_tables_v1.sql) | CMS 内容管理表 | + +#### Decoration 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/decoration/ak_diy_pages_v1.sql`](./10_schema/decoration/ak_diy_pages_v1.sql) | DIY 页面表 | + +#### Delivery 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/delivery/ak_delivery_system_v1.sql`](./10_schema/delivery/ak_delivery_system_v1.sql) | 配送系统表 | + +#### Distribution 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/distribution/ak_distribution_agents_v1.sql`](./10_schema/distribution/ak_distribution_agents_v1.sql) | 分销代理表 | +| [`10_schema/distribution/ak_distribution_agent_applications_v1.sql`](./10_schema/distribution/ak_distribution_agent_applications_v1.sql) | 代理申请表 | +| [`10_schema/distribution/ak_distribution_divisions_v1.sql`](./10_schema/distribution/ak_distribution_divisions_v1.sql) | 事业部表 | +| [`10_schema/distribution/ak_distribution_division_applications_v1.sql`](./10_schema/distribution/ak_distribution_division_applications_v1.sql) | 事业部申请表 | +| [`10_schema/distribution/ak_promoter_relations_v1.sql`](./10_schema/distribution/ak_promoter_relations_v1.sql) | 推广员关系表 | +| [`10_schema/distribution/ak_commission_logs_v1.sql`](./10_schema/distribution/ak_commission_logs_v1.sql) | 佣金日志表 | +| [`10_schema/distribution/ak_distribution_level_v1.sql`](./10_schema/distribution/ak_distribution_level_v1.sql) | 分销等级表 | +| [`10_schema/distribution/ak_distribution_config_v1.sql`](./10_schema/distribution/ak_distribution_config_v1.sql) | 分销配置表 | + +#### Finance 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/finance/ml_invoices_v1.sql`](./10_schema/finance/ml_invoices_v1.sql) | 发票表 | +| [`10_schema/finance/ml_extract_v1.sql`](./10_schema/finance/ml_extract_v1.sql) | 提现表 | +| [`10_schema/finance/ml_user_recharge_v1.sql`](./10_schema/finance/ml_user_recharge_v1.sql) | 用户充值表 | +| [`10_schema/finance/ml_user_bill_v1.sql`](./10_schema/finance/ml_user_bill_v1.sql) | 用户账单表 | + +#### Kefu 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/kefu/ml_kefu_tables_v1.sql`](./10_schema/kefu/ml_kefu_tables_v1.sql) | 客服系统表 | + +#### Marketing 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/marketing/ak_lottery_live_v1.sql`](./10_schema/marketing/ak_lottery_live_v1.sql) | 直播抽奖 | +| [`10_schema/marketing/ak_marketing_signin_logs_v1.sql`](./10_schema/marketing/ak_marketing_signin_logs_v1.sql) | 签到日志 | +| [`10_schema/marketing/ak_marketing_newcomer_config_v1.sql`](./10_schema/marketing/ak_marketing_newcomer_config_v1.sql) | 新人配置 | +| [`10_schema/marketing/ak_bargain_groupbuy_v1.sql`](./10_schema/marketing/ak_bargain_groupbuy_v1.sql) | 拼团砍价 | +| [`10_schema/marketing/ak_live_products_v1.sql`](./10_schema/marketing/ak_live_products_v1.sql) | 直播商品 | +| [`10_schema/marketing/ak_advanced_marketing_v1.sql`](./10_schema/marketing/ak_advanced_marketing_v1.sql) | 高级营销 | +| [`10_schema/marketing/ak_marketing_checkin_configs_v1.sql`](./10_schema/marketing/ak_marketing_checkin_configs_v1.sql) | 签到配置 | +| [`10_schema/marketing/ak_recharge_management_v1.sql`](./10_schema/marketing/ak_recharge_management_v1.sql) | 充值管理 | +| [`10_schema/marketing/ak_member_management_v1.sql`](./10_schema/marketing/ak_member_management_v1.sql) | 会员管理 | +| [`10_schema/marketing/ak_signin_configs_v1.sql`](./10_schema/marketing/ak_signin_configs_v1.sql) | 签到配置 | + +#### Order 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/order/ml_orders_schema_update_v1.sql`](./10_schema/order/ml_orders_schema_update_v1.sql) | 订单表结构更新 | + +#### Product 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/product/ml_products_ext_v1.sql`](./10_schema/product/ml_products_ext_v1.sql) | 商品扩展表 | +| [`10_schema/product/ak_shipping_templates_v1.sql`](./10_schema/product/ak_shipping_templates_v1.sql) | 运费模板 | +| [`10_schema/product/ak_product_member_prices_v1.sql`](./10_schema/product/ak_product_member_prices_v1.sql) | 会员价 | +| [`10_schema/product/ak_product_protections_v1.sql`](./10_schema/product/ak_product_protections_v1.sql) | 商品保障 | +| [`10_schema/product/ak_product_templates_v1.sql`](./10_schema/product/ak_product_templates_v1.sql) | 商品模板 | +| [`10_schema/product/ak_product_labels_v1.sql`](./10_schema/product/ak_product_labels_v1.sql) | 商品标签 | + +#### User 域 +| 文件路径 | 说明 | +|:---|:---| +| [`10_schema/user/ak_user_labels_v1.sql`](./10_schema/user/ak_user_labels_v1.sql) | 用户标签表 | +| [`10_schema/user/ak_user_groups_v1.sql`](./10_schema/user/ak_user_groups_v1.sql) | 用户分组表 | +| [`10_schema/user/ak_user_levels_v1.sql`](./10_schema/user/ak_user_levels_v1.sql) | 用户等级表 | + +--- + +## 第二阶段:RLS(在 Schema 之后执行) +> 目的:将各模块策略统一补齐 `deleted_at IS NULL`,确保默认查询排除已软删记录。 + +按域执行(域之间无强依赖,可按任意顺序;但**必须在 RPC 之前**执行): + +### Auth 域(新增) +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/auth/ak_auth_rls_v1.sql`](./20_rls/auth/ak_auth_rls_v1.sql) | 权限/角色表 RLS 策略(禁止直接访问) | + +### Admin 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/admin/ml_system_configs_rls_v1.sql`](./20_rls/admin/ml_system_configs_rls_v1.sql) | 系统配置 RLS | + +### CMS 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/cms/ml_cms_rls_v1.sql`](./20_rls/cms/ml_cms_rls_v1.sql) | CMS 内容 RLS | + +### Decoration 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/decoration/ml_decoration_rls_v1.sql`](./20_rls/decoration/ml_decoration_rls_v1.sql) | 装修 RLS | + +### Delivery 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/delivery/ak_delivery_rls_v1.sql`](./20_rls/delivery/ak_delivery_rls_v1.sql) | 配送 RLS | + +### Distribution 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/distribution/ml_distribution_rls_v1.sql`](./20_rls/distribution/ml_distribution_rls_v1.sql) | 分销 RLS | + +### Finance 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/finance/ml_extract_rls_v1.sql`](./20_rls/finance/ml_extract_rls_v1.sql) | 提现 RLS | +| [`20_rls/finance/ml_invoices_rls_v1.sql`](./20_rls/finance/ml_invoices_rls_v1.sql) | 发票 RLS | +| [`20_rls/finance/ml_user_bill_rls_v1.sql`](./20_rls/finance/ml_user_bill_rls_v1.sql) | 用户账单 RLS | +| [`20_rls/finance/ml_user_recharge_rls_v1.sql`](./20_rls/finance/ml_user_recharge_rls_v1.sql) | 用户充值 RLS | + +### Kefu 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/kefu/ml_kefu_rls_v1.sql`](./20_rls/kefu/ml_kefu_rls_v1.sql) | 客服 RLS | + +### Marketing 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/marketing/ml_marketing_others_rls_v1.sql`](./20_rls/marketing/ml_marketing_others_rls_v1.sql) | 其他营销 RLS | +| [`20_rls/marketing/ml_marketing_activities_rls_v1.sql`](./20_rls/marketing/ml_marketing_activities_rls_v1.sql) | 营销活动 RLS | +| [`20_rls/marketing/ml_coupon_templates_rls_v1.sql`](./20_rls/marketing/ml_coupon_templates_rls_v1.sql) | 优惠券模板 RLS | + +### User 域 +| 文件路径 | 说明 | +|:---|:---| +| [`20_rls/user/ak_user_labels_rls_v1.sql`](./20_rls/user/ak_user_labels_rls_v1.sql) | 用户标签 RLS | +| [`20_rls/user/ak_user_groups_rls_v1.sql`](./20_rls/user/ak_user_groups_rls_v1.sql) | 用户分组 RLS | +| [`20_rls/user/ak_user_levels_rls_v1.sql`](./20_rls/user/ak_user_levels_rls_v1.sql) | 用户等级 RLS | + +--- + +## 第三阶段:RPC(最后执行) +> 目的:将“删除”统一切换为软删除(写入 `deleted_at/deleted_by`),并在需要的函数中补齐级联软删。 + +按域执行(域之间一般无强依赖;但建议按下列顺序,先基础权限/用户,再业务模块): + +--- + +### 3.1 Admin 域(系统管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/admin/rpc_admin_get_system_info_v1.sql`](./30_rpc/admin/rpc_admin_get_system_info_v1.sql) | 获取系统信息 | +| [`30_rpc/admin/rpc_admin_get_overall_stats_v1.sql`](./30_rpc/admin/rpc_admin_get_overall_stats_v1.sql) | 获取整体统计 | +| [`30_rpc/admin/rpc_admin_system_config_get_v1.sql`](./30_rpc/admin/rpc_admin_system_config_get_v1.sql) | 获取系统配置 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/admin/rpc_admin_system_config_save_v1.sql`](./30_rpc/admin/rpc_admin_system_config_save_v1.sql) | 保存系统配置 | + +--- + +### 3.2 Auth 域(权限认证) + +#### 权限校验辅助函数(新增) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/auth/fn_check_admin_permission_v1.sql`](./30_rpc/auth/fn_check_admin_permission_v1.sql) | 通用 RBAC 权限校验函数 | + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/auth/rpc_admin_get_permission_list_v1.sql`](./30_rpc/auth/rpc_admin_get_permission_list_v1.sql) | 获取权限列表 | +| [`30_rpc/auth/rpc_admin_get_role_list_v1.sql`](./30_rpc/auth/rpc_admin_get_role_list_v1.sql) | 获取角色列表 | +| [`30_rpc/auth/rpc_admin_get_admin_list_v1.sql`](./30_rpc/auth/rpc_admin_get_admin_list_v1.sql) | 获取管理员列表 | +| [`30_rpc/auth/get_current_user_role_v1.sql`](./30_rpc/auth/get_current_user_role_v1.sql) | 获取当前用户角色 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/auth/rpc_admin_save_permission_v1.sql`](./30_rpc/auth/rpc_admin_save_permission_v1.sql) | 保存权限 | +| [`30_rpc/auth/rpc_admin_save_role_v1.sql`](./30_rpc/auth/rpc_admin_save_role_v1.sql) | 保存角色 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/auth/rpc_admin_delete_permission_v1.sql`](./30_rpc/auth/rpc_admin_delete_permission_v1.sql) | 删除权限(软删除) | +| [`30_rpc/auth/rpc_admin_delete_role_v1.sql`](./30_rpc/auth/rpc_admin_delete_role_v1.sql) | 删除角色(软删除) | + +#### 用户处理类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/auth/handle_new_user_v3.sql`](./30_rpc/auth/handle_new_user_v3.sql) | 处理新用户 v3 | +| [`30_rpc/auth/handle_new_user_v2.sql`](./30_rpc/auth/handle_new_user_v2.sql) | 处理新用户 v2 | + +--- + +### 3.3 CMS 域(内容管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/cms/rpc_admin_article_list_v1.sql`](./30_rpc/cms/rpc_admin_article_list_v1.sql) | 获取文章列表 | +| [`30_rpc/cms/rpc_admin_article_get_detail_v1.sql`](./30_rpc/cms/rpc_admin_article_get_detail_v1.sql) | 获取文章详情 | +| [`30_rpc/cms/rpc_admin_article_category_list_v1.sql`](./30_rpc/cms/rpc_admin_article_category_list_v1.sql) | 获取文章分类列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/cms/rpc_admin_article_save_v1.sql`](./30_rpc/cms/rpc_admin_article_save_v1.sql) | 保存文章 | +| [`30_rpc/cms/rpc_admin_article_category_save_v1.sql`](./30_rpc/cms/rpc_admin_article_category_save_v1.sql) | 保存文章分类 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/cms/rpc_admin_article_delete_v1.sql`](./30_rpc/cms/rpc_admin_article_delete_v1.sql) | 删除文章(软删除) | +| [`30_rpc/cms/rpc_admin_article_category_delete_v1.sql`](./30_rpc/cms/rpc_admin_article_category_delete_v1.sql) | 删除文章分类(软删除) | + +#### 状态管理类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/cms/rpc_admin_article_set_status_v1.sql`](./30_rpc/cms/rpc_admin_article_set_status_v1.sql) | 设置文章状态 | +| [`30_rpc/cms/rpc_admin_article_category_set_status_v1.sql`](./30_rpc/cms/rpc_admin_article_category_set_status_v1.sql) | 设置文章分类状态 | + +--- + +### 3.4 Decoration 域(装修管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/decoration/rpc_admin_get_diy_page_list_v1.sql`](./30_rpc/decoration/rpc_admin_get_diy_page_list_v1.sql) | 获取 DIY 页面列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/decoration/rpc_admin_save_diy_page_v1.sql`](./30_rpc/decoration/rpc_admin_save_diy_page_v1.sql) | 保存 DIY 页面 | +| [`30_rpc/decoration/rpc_admin_set_home_page_v1.sql`](./30_rpc/decoration/rpc_admin_set_home_page_v1.sql) | 设置首页 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql`](./30_rpc/decoration/rpc_admin_delete_diy_page_v1.sql) | 删除 DIY 页面(软删除) | + +--- + +### 3.5 Delivery 域(配送管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/delivery/rpc_admin_get_delivery_staff_list_v1.sql`](./30_rpc/delivery/rpc_admin_get_delivery_staff_list_v1.sql) | 获取配送员列表 | +| [`30_rpc/delivery/rpc_admin_get_delivery_station_list_v1.sql`](./30_rpc/delivery/rpc_admin_get_delivery_station_list_v1.sql) | 获取配送站点列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/delivery/rpc_admin_save_delivery_staff_v1.sql`](./30_rpc/delivery/rpc_admin_save_delivery_staff_v1.sql) | 保存配送员 | +| [`30_rpc/delivery/rpc_admin_save_delivery_station_v1.sql`](./30_rpc/delivery/rpc_admin_save_delivery_station_v1.sql) | 保存配送站点 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql`](./30_rpc/delivery/rpc_admin_delete_delivery_staff_v1.sql) | 删除配送员(软删除) | +| [`30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql`](./30_rpc/delivery/rpc_admin_delete_delivery_station_v1.sql) | 删除配送站点(软删除) | + +--- + +### 3.6 Distribution 域(分销管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/distribution/rpc_admin_get_agent_list_v1.sql`](./30_rpc/distribution/rpc_admin_get_agent_list_v1.sql) | 获取代理列表 | +| [`30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql`](./30_rpc/distribution/rpc_admin_get_agent_apply_list_v1.sql) | 获取代理申请列表 | +| [`30_rpc/distribution/rpc_admin_get_division_list_v1.sql`](./30_rpc/distribution/rpc_admin_get_division_list_v1.sql) | 获取事业部列表 | +| [`30_rpc/distribution/rpc_admin_get_promoter_list_v1.sql`](./30_rpc/distribution/rpc_admin_get_promoter_list_v1.sql) | 获取推广员列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/distribution/rpc_admin_save_agent_v1.sql`](./30_rpc/distribution/rpc_admin_save_agent_v1.sql) | 保存代理 | +| [`30_rpc/distribution/rpc_admin_save_division_v1.sql`](./30_rpc/distribution/rpc_admin_save_division_v1.sql) | 保存事业部 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/distribution/rpc_admin_delete_agent_v1.sql`](./30_rpc/distribution/rpc_admin_delete_agent_v1.sql) | 删除代理(软删除) | +| [`30_rpc/distribution/rpc_admin_delete_division_v1.sql`](./30_rpc/distribution/rpc_admin_delete_division_v1.sql) | 删除事业部(软删除) | + +#### 处理/审核类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql`](./30_rpc/distribution/rpc_admin_process_agent_apply_v1.sql) | 处理代理申请 | + +--- + +### 3.7 Finance 域(财务管理) + +#### 查询/统计类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/finance/rpc_admin_balance_stats_v1.sql`](./30_rpc/finance/rpc_admin_balance_stats_v1.sql) | 余额统计 | +| [`30_rpc/finance/rpc_admin_balance_distribution_v1.sql`](./30_rpc/finance/rpc_admin_balance_distribution_v1.sql) | 余额分布 | +| [`30_rpc/finance/rpc_admin_balance_trend_v1.sql`](./30_rpc/finance/rpc_admin_balance_trend_v1.sql) | 余额趋势 | +| [`30_rpc/finance/rpc_admin_finance_bill_summary_v1.sql`](./30_rpc/finance/rpc_admin_finance_bill_summary_v1.sql) | 财务账单汇总 | +| [`30_rpc/finance/rpc_admin_finance_overview_v1.sql`](./30_rpc/finance/rpc_admin_finance_overview_v1.sql) | 财务概览 | +| [`30_rpc/finance/rpc_admin_invoice_list_v1.sql`](./30_rpc/finance/rpc_admin_invoice_list_v1.sql) | 发票列表 | +| [`30_rpc/finance/rpc_admin_user_bill_list_v1.sql`](./30_rpc/finance/rpc_admin_user_bill_list_v1.sql) | 用户账单列表 | +| [`30_rpc/finance/rpc_admin_recharge_list_v1.sql`](./30_rpc/finance/rpc_admin_recharge_list_v1.sql) | 充值列表 | +| [`30_rpc/finance/rpc_admin_extract_list_v1.sql`](./30_rpc/finance/rpc_admin_extract_list_v1.sql) | 提现列表 | + +#### 处理/审核类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/finance/rpc_admin_invoice_process_v1.sql`](./30_rpc/finance/rpc_admin_invoice_process_v1.sql) | 处理发票 | +| [`30_rpc/finance/rpc_admin_recharge_audit_v1.sql`](./30_rpc/finance/rpc_admin_recharge_audit_v1.sql) | 审核充值 | +| [`30_rpc/finance/rpc_admin_extract_review_v1.sql`](./30_rpc/finance/rpc_admin_extract_review_v1.sql) | 审核提现 | + +--- + +### 3.8 Kefu 域(客服管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/kefu/rpc_admin_kefu_account_list_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_account_list_v1.sql) | 获取客服账号列表 | +| [`30_rpc/kefu/rpc_admin_kefu_auto_reply_list_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_auto_reply_list_v1.sql) | 获取自动回复列表 | +| [`30_rpc/kefu/rpc_admin_kefu_word_list_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_list_v1.sql) | 获取客服话术列表 | +| [`30_rpc/kefu/rpc_admin_kefu_word_category_list_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_category_list_v1.sql) | 获取话术分类列表 | +| [`30_rpc/kefu/rpc_admin_kefu_feedback_list_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_feedback_list_v1.sql) | 获取反馈列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/kefu/rpc_admin_kefu_account_save_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_account_save_v1.sql) | 保存客服账号 | +| [`30_rpc/kefu/rpc_admin_kefu_auto_reply_save_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_auto_reply_save_v1.sql) | 保存自动回复 | +| [`30_rpc/kefu/rpc_admin_kefu_word_save_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_save_v1.sql) | 保存客服话术 | +| [`30_rpc/kefu/rpc_admin_kefu_word_category_save_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_category_save_v1.sql) | 保存话术分类 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_account_delete_v1.sql) | 删除客服账号(软删除) | +| [`30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_auto_reply_delete_v1.sql) | 删除自动回复(软删除) | +| [`30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_delete_v1.sql) | 删除客服话术(软删除) | +| [`30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_word_category_delete_v1.sql) | 删除话术分类(软删除) | + +#### 状态/处理类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/kefu/rpc_admin_kefu_account_set_status_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_account_set_status_v1.sql) | 设置客服账号状态 | +| [`30_rpc/kefu/rpc_admin_kefu_auto_reply_set_status_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_auto_reply_set_status_v1.sql) | 设置自动回复状态 | +| [`30_rpc/kefu/rpc_admin_kefu_feedback_process_v1.sql`](./30_rpc/kefu/rpc_admin_kefu_feedback_process_v1.sql) | 处理客服反馈 | + +--- + +### 3.9 Marketing 域(营销管理) + +#### 查询/统计类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/marketing/rpc_admin_get_integral_stats_v1.sql`](./30_rpc/marketing/rpc_admin_get_integral_stats_v1.sql) | 获取积分统计 | + +--- + +### 3.10 Order 域(订单管理) + +#### 查询/统计类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/order/rpc_admin_order_list_v1.sql`](./30_rpc/order/rpc_admin_order_list_v1.sql) | 获取订单列表 | +| [`30_rpc/order/rpc_admin_order_source_stats_v1.sql`](./30_rpc/order/rpc_admin_order_source_stats_v1.sql) | 订单来源统计 | +| [`30_rpc/order/rpc_admin_order_type_stats_v1.sql`](./30_rpc/order/rpc_admin_order_type_stats_v1.sql) | 订单类型统计 | +| [`30_rpc/order/rpc_admin_order_stats_v1.sql`](./30_rpc/order/rpc_admin_order_stats_v1.sql) | 订单统计 | +| [`30_rpc/order/rpc_admin_order_trend_v1.sql`](./30_rpc/order/rpc_admin_order_trend_v1.sql) | 订单趋势 | +| [`30_rpc/order/rpc_admin_write_off_record_list_v1.sql`](./30_rpc/order/rpc_admin_write_off_record_list_v1.sql) | 核销记录列表 | +| [`30_rpc/order/rpc_admin_refund_order_list_v1.sql`](./30_rpc/order/rpc_admin_refund_order_list_v1.sql) | 退款订单列表 | +| [`30_rpc/order/rpc_admin_cashier_order_list_v1.sql`](./30_rpc/order/rpc_admin_cashier_order_list_v1.sql) | 收银订单列表 | + +--- + +### 3.11 Product 域(商品管理) + +#### 查询/统计类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/product/rpc_admin_get_product_reviews_v1.sql`](./30_rpc/product/rpc_admin_get_product_reviews_v1.sql) | 获取商品评价 | +| [`30_rpc/product/rpc_admin_product_count_stats_v1.sql`](./30_rpc/product/rpc_admin_product_count_stats_v1.sql) | 商品数量统计 | +| [`30_rpc/product/rpc_admin_product_analytics_v1.sql`](./30_rpc/product/rpc_admin_product_analytics_v1.sql) | 商品分析 | +| [`30_rpc/product/rpc_admin_product_trend_v1.sql`](./30_rpc/product/rpc_admin_product_trend_v1.sql) | 商品趋势 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/product/rpc_admin_category_delete_v1.sql`](./30_rpc/product/rpc_admin_category_delete_v1.sql) | 删除商品分类(软删除) | + +--- + +### 3.12 User 域(用户管理) + +#### 查询类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/user/rpc_admin_user_label_list_v1.sql`](./30_rpc/user/rpc_admin_user_label_list_v1.sql) | 获取用户标签列表 | +| [`30_rpc/user/rpc_admin_user_group_list_v1.sql`](./30_rpc/user/rpc_admin_user_group_list_v1.sql) | 获取用户分组列表 | +| [`30_rpc/user/rpc_admin_user_level_list_v1.sql`](./30_rpc/user/rpc_admin_user_level_list_v1.sql) | 获取用户等级列表 | + +#### 保存/更新类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/user/rpc_admin_user_label_save_v1.sql`](./30_rpc/user/rpc_admin_user_label_save_v1.sql) | 保存用户标签 | +| [`30_rpc/user/rpc_admin_user_group_save_v1.sql`](./30_rpc/user/rpc_admin_user_group_save_v1.sql) | 保存用户分组 | +| [`30_rpc/user/rpc_admin_user_level_save_v1.sql`](./30_rpc/user/rpc_admin_user_level_save_v1.sql) | 保存用户等级 | + +#### 删除类(软删除) +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/user/rpc_admin_user_label_delete_v1.sql`](./30_rpc/user/rpc_admin_user_label_delete_v1.sql) | 删除用户标签(软删除) | +| [`30_rpc/user/rpc_admin_user_group_delete_v1.sql`](./30_rpc/user/rpc_admin_user_group_delete_v1.sql) | 删除用户分组(软删除) | +| [`30_rpc/user/rpc_admin_user_level_delete_v1.sql`](./30_rpc/user/rpc_admin_user_level_delete_v1.sql) | 删除用户等级(软删除) | + +#### 状态/可见性管理类 +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/user/rpc_admin_user_label_set_status_v1.sql`](./30_rpc/user/rpc_admin_user_label_set_status_v1.sql) | 设置用户标签状态 | +| [`30_rpc/user/rpc_admin_user_group_set_status_v1.sql`](./30_rpc/user/rpc_admin_user_group_set_status_v1.sql) | 设置用户分组状态 | +| [`30_rpc/user/rpc_admin_user_level_set_status_v1.sql`](./30_rpc/user/rpc_admin_user_level_set_status_v1.sql) | 设置用户等级状态 | +| [`30_rpc/user/rpc_admin_user_level_set_visible_v1.sql`](./30_rpc/user/rpc_admin_user_level_set_visible_v1.sql) | 设置用户等级可见性 | + +--- + +### 3.13 Analytics 域(数据分析) + +| 文件路径 | 说明 | +|:---|:---| +| [`30_rpc/analytics/rpc_analytics_user_gender_distribution_v1.sql`](./30_rpc/analytics/rpc_analytics_user_gender_distribution_v1.sql) | 用户性别分布分析 | + +--- + +## 执行后验证清单 +- 字段存在性:抽查任一涉及表,确认 `deleted_at/deleted_by` 字段存在。 +- 索引存在性:确认 `idx_
_soft_delete` 存在。 +- RLS 生效:软删一条数据后,使用正常查询路径确认该条记录不可见。 +- RPC 行为:调用任一删除 RPC,确认: + - 记录未被物理删除; + - `deleted_at` 写入; + - `deleted_by` 写入(管理员 ID); + - 需要级联的对象其关联数据同步被软删。 diff --git a/pages/mall/admin/cms/category/list.uvue b/pages/mall/admin/cms/category/list.uvue index d38296a1..60bbfcdd 100644 --- a/pages/mall/admin/cms/category/list.uvue +++ b/pages/mall/admin/cms/category/list.uvue @@ -207,27 +207,42 @@ function handleEdit(item: ArticleCategory) { async function toggleStatus(item: ArticleCategory) { const targetStatus = item.status === 1 ? 0 : 1 - const ok = await setArticleCategoryStatus(item.id, targetStatus) - if (ok) { - item.status = targetStatus - uni.showToast({ title: '状态已更新' }) - } else { - uni.showToast({ title: '操作失败', icon: 'none' }) + try { + const resId = await saveArticleCategory( + item.id, + item.name, + item.icon, + item.sort, + targetStatus + ) + if (resId != null) { + item.status = targetStatus + uni.showToast({ title: '状态已更新' }) + } + } catch (e: any) { + const errMsg = e?.message || '操作失败' + uni.showToast({ title: errMsg, icon: 'none' }) } } async function handleDelete(item: ArticleCategory) { uni.showModal({ - title: '提示', - content: `确定要删除分类 "${item.name}" 吗?`, + title: '删除确认', + content: `确定要删除分类 "${item.name}" 吗?\n\n⚠️ 警告:该操作将同时删除该分类下的所有文章!`, + confirmText: '确认删除', + confirmColor: '#ed4014', success: async (res) => { if (res.confirm) { - const ok = await deleteArticleCategory(item.id) - if (ok) { - uni.showToast({ title: '删除成功' }) - loadData() - } else { - uni.showToast({ title: '删除失败', icon: 'none' }) + try { + const ok = await deleteArticleCategory(item.id) + if (ok) { + uni.showToast({ title: '删除成功' }) + loadData() + } + } catch (e: any) { + // 显示后端抛出的具体错误信息(如权限不足) + const errMsg = e?.message || '删除失败' + uni.showToast({ title: errMsg, icon: 'none', duration: 3000 }) } } } diff --git a/pages/mall/admin/product/classification/index.uvue b/pages/mall/admin/product/classification/index.uvue index 47ff2edd..a1a8452a 100644 --- a/pages/mall/admin/product/classification/index.uvue +++ b/pages/mall/admin/product/classification/index.uvue @@ -1,4 +1,4 @@ -