P12 | 标签体系:灵活的多维标签设计与前端联动

P12 | 标签体系:灵活的多维标签设计与前端联动

💰 付费文章 | 第二阶段:后端开发


标签体系的价值

标签是连接用户偏好和内容推荐的核心桥梁:

复制代码
用户偏好标签:["自然风光", "亲子游玩", "经济型"]
        ↕ 标签匹配
景点内容标签:["自然风光", "亲子友好", "免费入园"]
        ↓
推荐结果:匹配度排序

好的标签体系设计能同时支持:

  • 景点内容打标签
  • 用户偏好记录
  • 精准搜索过滤
  • 智能推荐排序

数据库设计

复制代码
-- 标签分类
CREATE TABLE `PLAT_TAG_CATEGORY` (
    `COMM_ID`    VARCHAR(32) NOT NULL PRIMARY KEY,
    `CATEGORY_NAME` VARCHAR(64) NOT NULL COMMENT '分类名',
    `SORT`       INT DEFAULT 0             COMMENT '排序',
    `FLAG`       INT DEFAULT 1
) COMMENT '标签分类';

-- 标签定义
CREATE TABLE `PLAT_TAG_DEFINITION` (
    `COMM_ID`       VARCHAR(32) NOT NULL PRIMARY KEY,
    `CATEGORY_ID`   VARCHAR(32) NOT NULL COMMENT '所属分类ID',
    `TAG_ID`        VARCHAR(64) NOT NULL COMMENT '标签英文标识(如 beach)',
    `TAG_NAME`      VARCHAR(64) NOT NULL COMMENT '标签中文名(如 海滩)',
    `ICON`          VARCHAR(128) COMMENT '图标URL',
    `SORT`          INT DEFAULT 0 COMMENT '排序',
    `FLAG`          INT DEFAULT 1,
    UNIQUE KEY `uk_category_tagId` (`CATEGORY_ID`, `TAG_ID`)
) COMMENT '标签定义';

-- 用户偏好记录
CREATE TABLE `TOWN_USER_PREFERENCE` (
    `COMM_ID`      VARCHAR(32) NOT NULL PRIMARY KEY,
    `USER_ID`      VARCHAR(32) NOT NULL COMMENT '用户ID',
    `TAG_ID`       VARCHAR(64) NOT NULL COMMENT '偏好标签ID',
    `ATTACH_DATA`  VARCHAR(128) COMMENT '附加数据(如出行人数=2)',
    `CREATE_TIME`  DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_user_tag` (`USER_ID`, `TAG_ID`)
) COMMENT '用户偏好';

初始化标签数据

复制代码
-- 7 大分类
INSERT INTO PLAT_TAG_CATEGORY (COMM_ID, CATEGORY_NAME, SORT) VALUES
('TC001', '城市特色', 100),
('TC002', '景点属性', 90),
('TC003', '体验特色', 80),
('TC004', '游客人群', 70),
('TC005', '消费特征', 60),
('TC006', '季节推荐', 50),
('TC007', '用户偏好', 40);

-- 城市特色标签
INSERT INTO PLAT_TAG_DEFINITION (COMM_ID, CATEGORY_ID, TAG_ID, TAG_NAME, SORT) VALUES
('TD001', 'TC001', 'coastal', '海滨城市', 1),
('TD002', 'TC001', 'mountain', '山岳城市', 2),
-- ... 更多标签

标签 API 接口

复制代码
// 获取所有标签(按分类分组)
@PostMapping("/web/user/preference/options")
fun getPreferenceOptions(): Result<*> {
    val categories = tagCategoryMapper.selectList(
        LambdaQueryWrapper<PlatTagCategoryEntity>()
            .eq(PlatTagCategoryEntity::flag, 1)
            .orderByDesc(PlatTagCategoryEntity::sort)
    )
    
    val result = categories.map { category ->
        val tags = tagDefinitionMapper.selectList(
            LambdaQueryWrapper<PlatTagDefinitionEntity>()
                .eq(PlatTagDefinitionEntity::categoryId, category.commId)
                .eq(PlatTagDefinitionEntity::flag, 1)
                .orderByAsc(PlatTagDefinitionEntity::sort)
        )
        
        mapOf(
            "categoryId" to category.commId,
            "categoryName" to category.categoryName,
            "tags" to tags.map { tag ->
                mapOf(
                    "tagId" to tag.tagId,
                    "tagName" to tag.tagName,
                    "icon" to (tag.icon ?: "")
                )
            }
        )
    }
    
    return Result.ok(result)
}

// 保存用户偏好
@PostMapping("/web/user/preference/save")
fun savePreference(@RequestBody params: Map<String, Any>): Result<*> {
    val currentUser = AuthInterceptor.currentUser.get() ?: return Result.unauthorized()
    @Suppress("UNCHECKED_CAST")
    val preferences = params["preferences"] as? List<Map<String, Any>> ?: emptyList()
    
    // 先删除旧偏好
    preferenceMapper.delete(
        LambdaQueryWrapper<TownUserPreferenceEntity>()
            .eq(TownUserPreferenceEntity::userId, currentUser.userId)
    )
    
    // 批量保存新偏好
    preferences.forEach { pref ->
        val entity = TownUserPreferenceEntity().apply {
            commId = IdGenerator.generate("UPF")
            userId = currentUser.userId
            tagId = pref["tagId"] as? String ?: ""
            attachData = pref["attachData"] as? String
            createTime = Date()
        }
        preferenceMapper.insert(entity)
    }
    
    return Result.ok(null, "保存成功")
}

基于标签的推荐

复制代码
// 根据用户偏好标签推荐景点
override fun recommendByPreference(userId: String, limit: Int): List<Map<String, Any?>> {
    // 1. 获取用户偏好标签
    val userTags = preferenceMapper.selectList(
        LambdaQueryWrapper<TownUserPreferenceEntity>()
            .eq(TownUserPreferenceEntity::userId, userId)
    ).map { it.tagId }.toSet()
    
    if (userTags.isEmpty()) {
        // 无偏好,返回热门景点
        return getHotAttractions(limit)
    }
    
    // 2. 查所有有效景点,计算匹配度
    val attractions = attractionMapper.selectList(
        LambdaQueryWrapper<TownAttractionEntity>()
            .eq(TownAttractionEntity::flag, 1)
    )
    
    return attractions
        .map { attraction ->
            val attractionTags = parseJsonList(attraction.tags).toSet()
            val matchCount = userTags.intersect(attractionTags).size
            Triple(attraction, matchCount, attractionTags)
        }
        .filter { (_, matchCount, _) -> matchCount > 0 }
        .sortedByDescending { (_, matchCount, _) -> matchCount }
        .take(limit)
        .map { (attraction, _, _) -> assembleAttractionCard(attraction) }
}

下一篇

P13 → 异步任务:后台长时间操作的最佳实践

相关推荐
小李子呢02112 小时前
前端八股浏览器网络(2)---cookie,localStorage,sessionStorage
前端·网络
小李子呢02112 小时前
前端八股Vue---插槽
前端·javascript·vue.js
学习使我健康2 小时前
Android 事件分发机制
android·java·前端
众少成多积小致巨2 小时前
libbinder_ndk 入门指南
前端·c++·架构
小李子呢02112 小时前
前端八股Vue---自定义组件(控件)
前端·javascript·vue.js
用户52709648744902 小时前
微前端(qiankun)单侧启动调试技巧
前端
斌味代码3 小时前
jQuery 内存泄漏排查:常见场景、工具使用与修复实战
前端·javascript·jquery
weixin199701080163 小时前
《爱回收商品详情页前端性能优化实战》
前端·性能优化
chenbin___3 小时前
鸿蒙(HarmonyOS)支持 useNativeDriver的详细说明(转自千问)
前端·javascript·react native·react.js·harmonyos