557 lines
13 KiB
Markdown
557 lines
13 KiB
Markdown
|
|
# 积分系统与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
|
|||
|
|
|