dimension-filter-design.md 6.1 KB

维度参数获取与筛选设计分析

概述

四个核心实体(活动、商品、文章、任务)都有"五维归属"的概念,但存储和查询方式各不相同。当前设计目标是:根据维度筛选时,只要该实体在对应维度的权重 > 0 就命中

实体维度字段对比

实体 表名 旧字段 新字段 维度筛选方式
Activity activities dimension_code (单值) dimension_weights (JSON) JSON_EXTRACT(dimension_weights, '$.{dim}') > 0 OR dimension_code = {dim}
Product products domain (单值) dimension_weights (JSON) JSON_EXTRACT(dimension_weights, '$.{dim}') > 0 OR domain = {dim}
Article articles related_dimensions (逗号分隔) dimension_weights (JSON) JSON_EXTRACT(dimension_weights, '$.{dim}') > 0 OR related_dimensions LIKE %{dim}%
Task tasks ❌ 不支持维度筛选

逐项分析

1. Activity(活动)✅ 已实现

实体字段:

private String dimensionCode;        // 旧: 单值, 如 "body"
private String dimensionWeights;     // 新: JSON, 如 {"body":30,"mind":0,"wisdom":0,"action":70,"wealth":0}

筛选逻辑(ActivityService.list() 第53-63行):

if (dimensionCode != null && !dimensionCode.isEmpty()) {
    query.and(w -> w
        .apply("COALESCE(JSON_EXTRACT(dimension_weights, '$." + dimensionCode + "'), 0) > 0")
        .or().eq(Activity::getDimensionCode, dimensionCode)
    );
}

状态: ✅ 已实现权重感知筛选,兼容旧版 dimensionCode

2. Product(商品)✅ 已实现

实体字段:

private String domain;               // 旧: 单值, 如 "body"
private String dimensionWeights;     // 新: JSON, 如 {"body":30,"mind":0,"wisdom":0,"action":70,"wealth":0}

筛选逻辑(ProductService.list() 第56-62行):

if (query.getDomain() != null && !query.getDomain().isEmpty()) {
    String domain = query.getDomain();
    wrapper.and(w -> w
        .apply("COALESCE(JSON_EXTRACT(dimension_weights, '$." + domain + "'), 0) > 0")
        .or().eq(Product::getDomain, domain)
    );
}

注意: 查询参数名为 domain(ProductListQueryDTO 中定义),前端传的是 dimensionCode 还是 domain 需要确认对齐。

状态: ✅ 已实现权重感知筛选,兼容旧版 domain

3. Article(文章)✅ 已实现

实体字段:

private String relatedDimensions;    // 旧: 逗号分隔, 如 "body,mind"
private String dimensionIds;         // 维度ID列表JSON
private String dimensionWeights;     // 新: JSON, 如 {"body":30,"mind":0,...}

筛选逻辑(ArticleService.getPublicList() 第93-98行):

if (dimensionCode != null && !dimensionCode.trim().isEmpty()) {
    wrapper.and(w -> w
        .apply("COALESCE(JSON_EXTRACT(dimension_weights, '$." + dimensionCode.trim() + "'), 0) > 0")
        .or().like(Article::getRelatedDimensions, dimensionCode.trim())
    );
}

自动生成权重的逻辑(ArticleService 第702-706行): 当管理员设置 relatedDimensions 时,自动调用 buildEqualWeights() 生成等比例权重:

private String buildEqualWeights(String relatedDimensions) {
    String[] parts = relatedDimensions.split(",");
    int equalWeight = 100 / parts.length;
    // 为每个维度分配 equalWeight
}

状态: ✅ 已实现权重感知筛选,兼容旧版 relatedDimensions

4. Task(任务)❌ 不支持维度筛选

实体字段: 无维度相关字段

Task 实体没有 dimensionCodedomaindimensionWeights 等任何维度字段。任务通过 category 字段分类(如"学习类""行动类"),但分类与五维维度没有直接映射关系。

状态: ❌ 不支持维度筛选

架构设计

JSON 权重格式

所有实体统一使用 JSON 格式存储五维权重:

{
  "body": 0,
  "mind": 30,
  "wisdom": 20,
  "action": 50,
  "wealth": 0
}
  • 权重值范围:0-100(整数)
  • 0 = 不关联该维度
  • > 0 = 关联该维度,值越大关联度越高
  • 所有权重之和不一定等于 100(可以只设置部分维度)

筛选原理

使用 MySQL 的 JSON_EXTRACT 函数:

COALESCE(JSON_EXTRACT(dimension_weights, '$.body'), 0) > 0
  • 如果 dimension_weights 为 NULL 或 JSON 中不含该 key,返回 0
  • 如果权重 > 0,表示该实体关联此维度,命中筛选

兼容旧数据

三种实体都保留了旧字段的兼容查询:

  • Activity: dimension_code 精确匹配
  • Product: domain 精确匹配
  • Article: related_dimensions LIKE 模糊匹配

前端调用方式

实体 列表接口 维度参数名 示例
活动 POST /api/activity/list dimensionCode {"dimensionCode":"body"}
商品 POST /api/product/list domain {"domain":"body"}
文章 POST /api/articles/list dimensionCode {"dimensionCode":"body"}
任务 POST /api/tasks/today 不支持

建议

统一参数名

商品列表使用 domain 而非 dimensionCode,与其他三个不统一。建议在 ProductListQueryDTO 中增加 dimensionCode 别名,内部映射到 domain

Task 增加维度支持

如果任务需要按维度筛选,有两种方案:

  1. 轻量方案:在 Task 实体增加 dimensionCode 字段(单值),通过 categorydimensionCode 映射表自动填充
  2. 完整方案:增加 dimensionWeights JSON 字段,与活动/商品/文章保持一致

创建/编辑时的维度设置

实体 创建接口 维度参数 说明
活动 POST /api/admin/activity/create dimensionCode + dimensionWeights 管理后台设置
商品 POST /api/product/create domain + dimensionWeights 管理后台设置
文章 POST /api/admin/articles/create relatedDimensions 自动生成 dimensionWeights
任务 POST /api/tasks/create 不支持 通过 category 间接关联