Files
sionrui/openspec/plans/points-oss-quota-system.md
2026-02-25 21:30:24 +08:00

14 KiB
Raw Blame History

积分系统与OSS额度系统实施计划

创建日期2026-02-25 状态:待审批


一、需求概述

1.1 业务需求

模块 需求描述
前端积分系统 前端启动时加载 muye_ai_model_config,根据 model_code 获取积分消耗,显示在对标分析、热点趋势、智能体、数字人模块
OSS额度系统 后端统一用字节计算,仅统计已上传未删除文件,上传前校验,删除后释放,每日凌晨自动对账
OSS存储管理 素材列表和个人中心显示用户正确的额度

1.2 验收标准

  1. 前端各业务模块正确显示积分消耗数值
  2. OSS 上传前校验额度,超额拒绝上传
  3. OSS 文件删除后实时释放额度
  4. 每日凌晨自动对账,确保数据库记录与实际一致

二、现有架构分析

2.1 已有基础设施

组件 位置 状态
积分服务 PointsService 已实现预检、扣减、预扣逻辑
AI模型配置 muye_ai_model_config 已有 consume_points 字段
用户档案服务 MemberUserProfileService ⚠️ 需增强存储校验和更新
用户档案表 muye_member_user_profile 已有存储和积分字段GB单位
用户Store stores/user.js 已有积分和存储计算属性
个人中心 views/user/Profile.vue 已显示基本额度信息

2.2 需要新增的组件

组件 类型 说明
ModelConfigApi 前端API 获取模型配置列表
PointsConfigStore 前端Store 管理积分配置状态
PointsTag 前端组件 积分消耗标签组件
OssQuotaReconcileJob 后端Job 每日对账任务
StorageQuotaMixin 前端Composable 存储额度显示逻辑复用

三、详细设计

3.1 前端积分系统

3.1.1 数据流

前端启动 → 调用 API 获取模型配置 → 存入 Store → 各模块从 Store 读取显示

3.1.2 新增文件

frontend/app/web-gold/src/
├── api/
│   └── modelConfig.js              # 模型配置 API
├── stores/
│   └── pointsConfig.js             # 积分配置 Store
└── components/
    └── common/
        └── PointsTag.vue           # 积分消耗标签组件

3.1.3 API 设计

// api/modelConfig.js
export function getModelConfigList() {
  return request({
    url: `${BASE_URL}/aimodelconfig/list-enabled`,
    method: 'get'
  })
}

// 返回格式
{
  "dify": {
    "agent_chat_pro": { "consumePoints": 10, "modelName": "深度版" },
    "agent_chat_standard": { "consumePoints": 5, "modelName": "标准版" }
  },
  "digital_human": {
    "latentsync": { "consumePoints": 50, "modelName": "数字人" }
  },
  // ...
}

3.1.4 Store 设计

// stores/pointsConfig.js
export const usePointsConfigStore = defineStore('pointsConfig', () => {
  const configMap = ref({})        // 按平台+modelCode组织的配置
  const isLoaded = ref(false)

  // 根据 platform 和 modelCode 获取积分消耗
  const getConsumePoints = (platform, modelCode) => {
    return configMap.value[platform]?.[modelCode]?.consumePoints ?? 0
  }

  // 初始化加载配置
  const loadConfig = async () => {
    if (isLoaded.value) return
    const data = await getModelConfigList()
    configMap.value = data
    isLoaded.value = true
  }

  return { configMap, isLoaded, getConsumePoints, loadConfig }
})

3.1.5 积分标签组件

<!-- components/common/PointsTag.vue -->
<template>
  <span class="points-tag" :class="{ 'is-loading': loading }">
    <ThunderboltOutlined />
    <span class="points-value">{{ displayPoints }}</span>
    <span class="points-unit">积分</span>
  </span>
</template>

<script setup>
import { computed } from 'vue'
import { usePointsConfigStore } from '@/stores/pointsConfig'

const props = defineProps({
  platform: { type: String, required: true },
  modelCode: { type: String, required: true },
  loading: { type: Boolean, default: false }
})

const pointsConfigStore = usePointsConfigStore()
const displayPoints = computed(() =>
  pointsConfigStore.getConsumePoints(props.platform, props.modelCode)
)
</script>

3.1.6 各模块集成点

模块 文件位置 集成方式
智能体 ChatDrawer.vue 在发送按钮旁显示积分消耗
数字人 Video.vue 在生成按钮旁显示积分消耗
对标分析 Benchmark.vue 在分析按钮旁显示积分消耗
热点趋势 Forecast.vue 在文案生成按钮旁显示积分消耗

3.2 OSS 额度系统

3.2.1 数据模型

MemberUserProfileDO 字段muye_member_user_profile 表):

private String userId;              // 用户ID
private BigDecimal totalStorage;    // 云空间总容量 (GB)
private BigDecimal usedStorage;     // 云空间已用容量 (GB)
private BigDecimal remainingStorage;// 云空间剩余容量 (GB)
private Integer totalPoints;        // 账户总积分
private Integer usedPoints;         // 账户消耗积分
private Integer remainingPoints;    // 账户剩余积分

设计决策:保持 GB 单位存储(兼容现有数据),后端逻辑统一用字节计算

存储文件记录表muye_material_file

-- 文件大小字段(字节)
file_size BIGINT NOT NULL COMMENT '文件大小(字节)'

单位转换常量:

// 1 GB = 1024 * 1024 * 1024 字节
public static final long BYTES_PER_GB = 1073741824L;

3.2.2 后端增强设计

修改/新增文件:

yudao-module-tik/src/main/java/cn/iocoder/yudao/module/tik/
├── muye/
│   └── memberuserprofile/
│       ├── service/
│       │   ├── MemberUserProfileService.java        # 增强:添加存储额度方法
│       │   └── MemberUserProfileServiceImpl.java
│       ├── mapper/
│       │   └── MemberUserProfileMapper.java         # 增强:添加原子更新方法
│       └── job/
│           └── OssQuotaReconcileJob.java            # 新增每日对账Job

3.2.3 额度校验流程

用户上传文件
    ↓
检查文件大小(字节)
    ↓
调用 MemberUserProfileService.validateStorage(userId, fileSizeBytes)
    ↓
    内部逻辑:将 fileSizeBytes 转换为 GB与 remainingStorage 比较
    ↓
├── 余额充足 → 允许上传
│       ↓
│   文件上传成功 → 调用 increaseUsedStorage(userId, fileSizeBytes)
│
└── 余额不足 → 抛出异常,拒绝上传

3.2.4 额度释放流程

用户删除文件
    ↓
获取文件大小 fileSizeBytes字节
    ↓
调用 MemberUserProfileService.decreaseUsedStorage(userId, fileSizeBytes)
    ↓
内部逻辑字节转GB原子更新 usedStorage/remainingStorage

3.2.5 MemberUserProfileService 增强

// 新增方法
/**
 * 校验存储空间是否足够
 * @param userId 用户ID
 * @param fileSizeBytes 文件大小(字节)
 */
void validateStorage(String userId, long fileSizeBytes);

/**
 * 增加已使用存储空间
 * @param userId 用户ID
 * @param fileSizeBytes 文件大小(字节)
 */
void increaseUsedStorage(String userId, long fileSizeBytes);

/**
 * 减少已使用存储空间
 * @param userId 用户ID
 * @param fileSizeBytes 文件大小(字节)
 */
void decreaseUsedStorage(String userId, long fileSizeBytes);

3.2.6 Mapper 原子更新

// MemberUserProfileMapper.java 新增

/**
 * 原子增加已用存储(乐观锁)
 * @param userId 用户ID
 * @param storageGb 增加的存储量GBBigDecimal转String
 * @return 影响行数0表示余额不足
 */
@Update("UPDATE muye_member_user_profile " +
        "SET used_storage = used_storage + #{storageGb}, " +
        "    remaining_storage = remaining_storage - #{storageGb}, " +
        "    update_time = NOW() " +
        "WHERE user_id = #{userId} AND remaining_storage >= #{storageGb}")
int updateStorageIncrease(@Param("userId") String userId, @Param("storageGb") String storageGb);

/**
 * 原子减少已用存储
 */
@Update("UPDATE muye_member_user_profile " +
        "SET used_storage = used_storage - #{storageGb}, " +
        "    remaining_storage = remaining_storage + #{storageGb}, " +
        "    update_time = NOW() " +
        "WHERE user_id = #{userId} AND used_storage >= #{storageGb}")
int updateStorageDecrease(@Param("userId") String userId, @Param("storageGb") String storageGb);

3.2.7 每日对账Job

@Component
public class OssQuotaReconcileJob {

    @Scheduled(cron = "0 0 3 * * ?") // 每日凌晨3点
    public void reconcile() {
        // 1. 查询所有用户的档案
        // 2. 统计每个用户 muye_material_file 表中文件总大小(字节)
        // 3. 转换为 GB与 usedStorage 对比
        // 4. 不一致则修正并记录日志
    }
}

3.2.8 API 设计

使用现有的 /webApi/api/tik/member-profile/get 接口,返回数据已包含存储额度信息。


---

### 3.3 OSS 存储管理

#### 3.3.1 素材列表集成

在 `MaterialListNew.vue` 顶部工具栏显示存储额度:

```vue
<div class="storage-quota-info">
  <DatabaseOutlined />
  <span>{{ formatStorage(usedStorage) }} / {{ formatStorage(totalStorage) }}</span>
  <a-progress :percent="storagePercent" size="small" />
</div>

3.3.2 个人中心优化

Profile.vue 已有存储空间显示,需要确保数据来源正确:

  • userStore.remainingStorage 读取
  • 确保 getUserProfile() API 返回正确的字节数据

四、任务分解

Phase 1: 后端基础(优先级:高)

# 任务 文件 说明
1.1 新增模型配置列表API AppAiModelConfigController.java 添加 /list-enabled 接口
1.2 增强用户档案服务 MemberUserProfileServiceImpl.java 添加存储校验和更新方法
1.3 新增Mapper原子更新 MemberUserProfileMapper.java 添加存储增减的原子操作
1.4 新增每日对账Job OssQuotaReconcileJob.java 实现自动对账

Phase 2: 前端积分系统(优先级:高)

# 任务 文件 说明
2.1 创建模型配置API api/modelConfig.js 封装配置获取接口
2.2 创建积分配置Store stores/pointsConfig.js 管理配置状态
2.3 创建积分标签组件 components/common/PointsTag.vue 可复用的积分显示组件
2.4 应用启动时加载配置 App.vue 或入口文件 初始化积分配置

Phase 3: 业务模块集成(优先级:中)

# 任务 文件 说明
3.1 智能体模块集成 ChatDrawer.vue 显示对话积分消耗
3.2 数字人模块集成 dh/Video.vue 显示生成积分消耗
3.3 对标分析集成 Benchmark.vue 显示分析积分消耗
3.4 热点趋势集成 Forecast.vue 显示文案生成积分消耗

Phase 4: OSS存储管理优先级

# 任务 文件 说明
4.1 素材列表显示额度 MaterialListNew.vue 工具栏显示存储额度
4.2 上传前额度校验 useUpload.js 上传前检查额度
4.3 删除后释放额度 后端文件删除接口 调用 decreaseUsedStorage
4.4 个人中心优化 Profile.vue 确保显示正确字节值

五、风险分析

风险 影响 对策
积分配置加载失败 前端显示0积分 添加默认值和重试机制
并发上传导致额度超限 超出配额 使用数据库乐观锁或原子操作
对账Job执行时间过长 影响系统性能 分批处理,添加超时控制
历史数据不一致 对账修正幅度大 先做数据盘点,再逐步修正

六、测试计划

6.1 单元测试

  • PointsConfigStore 状态管理测试
  • TikUserQuotaService 额度计算测试
  • 对账Job逻辑测试

6.2 集成测试

  • 前端积分显示正确性
  • OSS上传额度校验
  • OSS删除额度释放
  • 每日对账执行

6.3 验收测试

Benchark Checklist:
1. ✅ 智能体对话页面显示"消耗 X 积分"
2. ✅ 数字人生成页面显示"消耗 X 积分"
3. ✅ 对标分析页面显示"消耗 X 积分"
4. ✅ 热点趋势页面显示"消耗 X 积分"
5. ✅ 素材列表显示存储额度进度条
6. ✅ 上传大文件超出额度时提示错误
7. ✅ 删除文件后额度正确释放
8. ✅ 个人中心显示正确的存储额度

七、实施顺序建议

Week 1: Phase 1 (后端基础) + Phase 2 (前端积分系统)
Week 2: Phase 3 (业务模块集成) + Phase 4 (OSS存储管理)

八、相关文件清单

后端文件

操作 文件路径
修改 yudao-module-tik/.../aimodelconfig/AiModelConfigController.java
修改 yudao-module-tik/.../memberuserprofile/service/MemberUserProfileService.java
修改 yudao-module-tik/.../memberuserprofile/service/MemberUserProfileServiceImpl.java
修改 yudao-module-tik/.../memberuserprofile/mapper/MemberUserProfileMapper.java
新增 yudao-module-tik/.../memberuserprofile/job/OssQuotaReconcileJob.java

前端文件

操作 文件路径
新增 frontend/app/web-gold/src/api/modelConfig.js
新增 frontend/app/web-gold/src/stores/pointsConfig.js
新增 frontend/app/web-gold/src/components/common/PointsTag.vue
修改 frontend/app/web-gold/src/views/agents/ChatDrawer.vue
修改 frontend/app/web-gold/src/views/dh/Video.vue
修改 frontend/app/web-gold/src/views/content-style/Benchmark.vue
修改 frontend/app/web-gold/src/views/trends/Forecast.vue
修改 frontend/app/web-gold/src/views/material/MaterialListNew.vue
修改 frontend/app/web-gold/src/composables/useUpload.js