# 积分系统与AI模型管理完整功能总结

## 📋 目录
1. [积分消费查询功能](#1-积分消费查询功能)
2. [AI模型列表查询功能](#2-ai模型列表查询功能)
3. [数据库迁移脚本](#3-数据库迁移脚本)
4. [API接口列表](#4-api接口列表)
5. [前端调用示例](#5-前端调用示例)

---

## 1. 积分消费查询功能

### 功能概述
用户可以查看自己的积分余额、消费明细和统计信息。

### 核心接口

#### 1.1 获取积分余额
```http
GET /user/points/consumption/balance
Authorization: Bearer {token}
```

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "currentPoints": 1500,
    "pointsExpiresAt": "2025-12-31T23:59:59",
    "willExpireSoon": false,
    "daysUntilExpire": 120
  }
}
```

#### 1.2 获取积分消费记录（分页）
```http
GET /user/points/consumption/logs?page=1&size=10&changeType=consume
Authorization: Bearer {token}
```

**参数说明：**
- `page`: 页码（默认1）
- `size`: 每页数量（默认10，最大100）
- `changeType`: 变动类型（可选）
  - `recharge`: 充值
  - `consume`: 消费
  - `refund`: 退款
  - `admin_adjust`: 管理员调整

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "records": [
      {
        "id": 1,
        "taskNo": "TASK202510221234567890",
        "changeType": "consume",
        "changeTypeName": "消费",
        "changeAmount": -10,
        "balanceBefore": 1510,
        "balanceAfter": 1500,
        "description": "AI图片生成消费",
        "createTime": "2025-10-22T10:30:00"
      }
    ],
    "total": 100,
    "page": 1,
    "size": 10,
    "totalPages": 10
  }
}
```

#### 1.3 获取积分统计
```http
GET /user/points/consumption/stats
Authorization: Bearer {token}
```

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "currentPoints": 1500,
    "totalRechargePoints": 2000,
    "totalConsumePoints": 500,
    "totalRefundPoints": 0,
    "pointsExpiresAt": "2025-12-31T23:59:59"
  }
}
```

---

## 2. AI模型列表查询功能

### 功能概述
用户可以查看系统中所有可用的AI模型，支持按任务类型、厂商分组查询。

### 任务类型分类

#### 细致分类（数据库 task_type 字段）
- `text_to_image`: 文生图
- `image_to_image`: 图生图
- `text_to_video`: 文生视频
- `image_to_video`: 图生视频
- `llm`: 大语言模型
- `text_to_audio`: 文生音频
- `image_to_text`: 图生文
- `other`: 其他

#### 粗略分类（兼容旧接口）
- `image`: 图片生成（包括 text_to_image 和 image_to_image）
- `video`: 视频生成（包括 text_to_video 和 image_to_video）
- `audio`: 音频生成
- `text`: 文本生成

### 核心接口

#### 2.1 获取模型列表（支持筛选）
```http
GET /user/ai/models?taskType=text_to_image&provider=openai&enabledOnly=true
```

**参数说明：**
- `taskType`: 任务类型（可选）
- `provider`: 服务提供商（可选：openai/runninghub）
- `enabledOnly`: 是否只返回已启用的模型（默认true）

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": [
    {
      "id": 1,
      "modelName": "sora_image",
      "displayName": "Sora高质量图片生成",
      "description": "Sora高质量图片生成",
      "pointsCost": 11,
      "providerType": "openai",
      "taskType": "text_to_image",
      "isEnabled": true,
      "extendedConfig": {}
    }
  ]
}
```

#### 2.2 按任务类型分组获取模型
```http
GET /user/ai/models/group-by-type?provider=&enabledOnly=true
```

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": [
    {
      "taskType": "text_to_image",
      "taskTypeName": "文生图",
      "models": [
        {
          "id": 1,
          "modelName": "sora_image",
          "displayName": "Sora高质量图片生成",
          "pointsCost": 11,
          "providerType": "openai",
          "taskType": "text_to_image",
          "isEnabled": true
        }
      ],
      "count": 2
    },
    {
      "taskType": "text_to_video",
      "taskTypeName": "文生视频",
      "models": [...],
      "count": 4
    }
  ]
}
```

#### 2.3 按厂商分组获取模型
```http
GET /user/ai/models/group-by-provider?taskType=&enabledOnly=true
```

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": [
    {
      "providerType": "openai",
      "providerName": "OpenAI",
      "models": [...],
      "count": 3
    },
    {
      "providerType": "runninghub",
      "providerName": "RunningHub",
      "models": [...],
      "count": 5
    }
  ]
}
```

#### 2.4 获取模型统计
```http
GET /user/ai/models/stats
```

**响应示例：**
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "totalModels": 10,
    "enabledModels": 8,
    "countByType": {
      "text_to_image": 2,
      "text_to_video": 4,
      "image_to_video": 1,
      "llm": 1
    },
    "countByProvider": {
      "openai": 3,
      "runninghub": 5
    }
  }
}
```

---

## 3. 数据库迁移脚本

### V6: 积分充值系统
- 创建 `points_package` 表（积分套餐）
- 扩展 `order` 表支持积分订单
- 更新 `points_consumption_log` 表支持充值类型

### V7: 任务类型细分
- 为 `points_config` 表添加 `task_type` 字段
- 根据现有模型名称更新任务类型
- 添加多种模型类型示例数据
- 添加索引优化查询性能

---

## 4. API接口列表

### 4.1 积分消费查询（需要登录）
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取积分余额 | GET | `/user/points/consumption/balance` | 当前积分和过期时间 |
| 获取消费记录 | GET | `/user/points/consumption/logs` | 分页查询消费明细 |
| 获取积分统计 | GET | `/user/points/consumption/stats` | 累计充值、消费、退款 |

### 4.2 AI模型查询（公开访问）
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取模型列表 | GET | `/user/ai/models` | 支持筛选和过滤 |
| 按类型分组 | GET | `/user/ai/models/group-by-type` | 按任务类型分组 |
| 按厂商分组 | GET | `/user/ai/models/group-by-provider` | 按服务提供商分组 |
| 获取统计信息 | GET | `/user/ai/models/stats` | 模型数量统计 |

---

## 5. 前端调用示例

### 5.1 Vue 3 + TypeScript 示例

```typescript
// api/points.ts
import request from '@/utils/request'

// 获取积分余额
export function getPointsBalance() {
  return request({
    url: '/user/points/consumption/balance',
    method: 'get'
  })
}

// 获取积分消费记录
export function getConsumptionLogs(params: {
  page?: number
  size?: number
  changeType?: 'recharge' | 'consume' | 'refund' | 'admin_adjust'
}) {
  return request({
    url: '/user/points/consumption/logs',
    method: 'get',
    params
  })
}

// 获取积分统计
export function getConsumptionStats() {
  return request({
    url: '/user/points/consumption/stats',
    method: 'get'
  })
}

// api/models.ts
import request from '@/utils/request'

// 获取所有模型
export function getAllModels(params: {
  taskType?: string
  provider?: string
  enabledOnly?: boolean
}) {
  return request({
    url: '/user/ai/models',
    method: 'get',
    params
  })
}

// 按类型分组获取模型
export function getModelsByType(params: {
  provider?: string
  enabledOnly?: boolean
}) {
  return request({
    url: '/user/ai/models/group-by-type',
    method: 'get',
    params
  })
}

// 按厂商分组获取模型
export function getModelsByProvider(params: {
  taskType?: string
  enabledOnly?: boolean
}) {
  return request({
    url: '/user/ai/models/group-by-provider',
    method: 'get',
    params
  })
}

// 获取模型统计
export function getModelStats() {
  return request({
    url: '/user/ai/models/stats',
    method: 'get'
  })
}
```

### 5.2 React 示例

```typescript
// hooks/usePoints.ts
import { useState, useEffect } from 'react'
import { getPointsBalance, getConsumptionLogs } from '@/api/points'

export function usePointsBalance() {
  const [balance, setBalance] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    getPointsBalance().then(res => {
      setBalance(res.data)
      setLoading(false)
    })
  }, [])

  return { balance, loading }
}

// hooks/useModels.ts
import { useState, useEffect } from 'react'
import { getModelsByType } from '@/api/models'

export function useModelsByType(provider?: string) {
  const [models, setModels] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    getModelsByType({ provider, enabledOnly: true }).then(res => {
      setModels(res.data)
      setLoading(false)
    })
  }, [provider])

  return { models, loading }
}
```

### 5.3 使用场景示例

```vue
<!-- PointsBalance.vue -->
<template>
  <div class="points-balance">
    <div class="balance-card">
      <h3>当前积分</h3>
      <p class="points">{{ balance?.currentPoints || 0 }}</p>
      <p class="expire-info" v-if="balance?.pointsExpiresAt">
        {{ balance.willExpireSoon ? '即将过期' : '有效期至' }}：
        {{ formatDate(balance.pointsExpiresAt) }}
      </p>
    </div>
    
    <div class="consumption-logs">
      <h3>消费记录</h3>
      <div v-for="log in logs.records" :key="log.id" class="log-item">
        <span>{{ log.changeTypeName }}</span>
        <span>{{ log.description }}</span>
        <span :class="log.changeAmount > 0 ? 'increase' : 'decrease'">
          {{ log.changeAmount > 0 ? '+' : '' }}{{ log.changeAmount }}
        </span>
        <span>{{ formatDate(log.createTime) }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getPointsBalance, getConsumptionLogs } from '@/api/points'

const balance = ref(null)
const logs = ref({ records: [], total: 0 })

onMounted(async () => {
  const [balanceRes, logsRes] = await Promise.all([
    getPointsBalance(),
    getConsumptionLogs({ page: 1, size: 10 })
  ])
  balance.value = balanceRes.data
  logs.value = logsRes.data
})
</script>
```

```vue
<!-- ModelSelector.vue -->
<template>
  <div class="model-selector">
    <div class="filter">
      <select v-model="selectedType">
        <option value="">全部类型</option>
        <option value="text_to_image">文生图</option>
        <option value="text_to_video">文生视频</option>
        <option value="image_to_video">图生视频</option>
      </select>
      
      <select v-model="selectedProvider">
        <option value="">全部厂商</option>
        <option value="openai">OpenAI</option>
        <option value="runninghub">RunningHub</option>
      </select>
    </div>
    
    <div class="models-grid">
      <div v-for="model in filteredModels" :key="model.id" class="model-card">
        <h4>{{ model.displayName }}</h4>
        <p>{{ model.description }}</p>
        <div class="model-footer">
          <span class="cost">{{ model.pointsCost }} 积分</span>
          <span class="provider">{{ model.providerType }}</span>
        </div>
        <button @click="selectModel(model)">选择</button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { getAllModels } from '@/api/models'

const selectedType = ref('')
const selectedProvider = ref('')
const allModels = ref([])

const filteredModels = computed(() => {
  return allModels.value.filter(model => {
    const typeMatch = !selectedType.value || model.taskType === selectedType.value
    const providerMatch = !selectedProvider.value || model.providerType === selectedProvider.value
    return typeMatch && providerMatch
  })
})

onMounted(async () => {
  const res = await getAllModels({ enabledOnly: true })
  allModels.value = res.data
})

const selectModel = (model) => {
  // 选择模型逻辑
  console.log('Selected model:', model)
}
</script>
```

---

## 📌 总结

### 已实现功能
1. ✅ 用户积分余额查询
2. ✅ 用户积分消费记录查询（分页、筛选）
3. ✅ 用户积分统计信息
4. ✅ AI模型列表查询（支持筛选）
5. ✅ 按任务类型分组查询模型
6. ✅ 按厂商分组查询模型
7. ✅ 模型统计信息

### 技术特点
- 支持细致的任务类型分类（文生图、图生图、图生视频等）
- 向后兼容粗略分类（image、video等）
- 公开访问的模型列表接口，无需登录
- 需要登录的积分查询接口，保护用户隐私
- 完整的分页支持
- 灵活的筛选和分组功能

### 数据库优化
- 添加 `task_type` 字段用于精确分类
- 添加索引提升查询性能
- 支持逻辑删除

### 安全性
- 积分相关接口需要用户认证
- 模型列表接口公开访问，方便前端展示
- 完整的权限控制配置

---

**文档版本：** v1.0  
**最后更新：** 2025-10-22

