feat: 单活动直达详情并隐藏返回;补充C端额度查询对接文档
Made-with: Cursor
This commit is contained in:
parent
fe210b52ee
commit
fb2789f752
412
docs/design/create/18_C端额度查询接口对接文档_V1.0.md
Normal file
412
docs/design/create/18_C端额度查询接口对接文档_V1.0.md
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
# C 端用户额度查询接口对接文档 V2.1
|
||||||
|
|
||||||
|
> **接口路径**:`GET /api/v1/quota/user`
|
||||||
|
> **适用端**:H5 / Android / 小程序 / iOS
|
||||||
|
> **签名方式**:⚡ **无需 HMAC 签名**(服务端已对此路径加白名单)
|
||||||
|
> **更新时间**:2026-04-18
|
||||||
|
> **变更说明**:V2.1 用户级闸门版——补回 V2.0 误删的 `userCallLimit/userCallRemaining` 两字段,**对齐后端真实校验链路**,避免「前端绿灯进入 → 创作时被拒」的体验断崖
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 为什么有 V2.1(必读)
|
||||||
|
|
||||||
|
V2.0 把字段从 12 个砍到 6 个,初衷是"机构没额度=用户没额度"够用了。但**遗漏了反命题**:
|
||||||
|
|
||||||
|
> **机构有额度 ≠ 用户有额度**
|
||||||
|
|
||||||
|
实际系统里,`QuotaService.checkUserCallLimit(orgId, phone, periodId)` 会按 (机构+手机+周期) 三元组撞 Redis 用户级上限(默认 5 次/用户/周期,可在 `t_org.user_call_limit` 或 `t_config.user_call_limit` 调整)。V2.0 接口不返用户级数据 → 前端拿 `canCreate=true` 进创作页 → 真正的 `POST /creation/image-story` 被 `QUOTA_EXCEEDED` 拒掉 → 用户懵 → 客服压力。
|
||||||
|
|
||||||
|
V2.1 修复:**接口返回 `userCallLimit/userCallRemaining` + canCreate 决策升级为四闸门**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 用途
|
||||||
|
|
||||||
|
让 C 端用户在**进入创作前**一次拿到三件事:**能不能创作 + 还剩多少 + 不能为什么**。
|
||||||
|
|
||||||
|
`canCreate` 字段一键决定是否放行创作入口;`summary` 字段是服务端拼好的友好文案,前端直接 Toast 即可,无需自己拼字符串。
|
||||||
|
|
||||||
|
**典型触发场景**:
|
||||||
|
- 首页 / 创作中心点击"+"号开始新作品
|
||||||
|
- 进入画一画页面前的额度预检
|
||||||
|
- 创作历史页"重新创作"按钮的可用性判断
|
||||||
|
|
||||||
|
**额度范围**:仅 A1/A2/A3 主创作(绘本生成)。A6/A7 角色提取重建有独立 `a5_user_call_limit` 池,不在此接口范围。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 请求
|
||||||
|
|
||||||
|
### 2.1 端点
|
||||||
|
|
||||||
|
```
|
||||||
|
GET http://121.40.20.224:8267/api/v1/quota/user
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 请求头
|
||||||
|
|
||||||
|
| Header | 必填 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `Content-Type` | 否 | GET 无 body,可省略 |
|
||||||
|
| `X-App-Key` / `X-Signature` / `X-Timestamp` / `X-Nonce` | **否** | **此接口不校验 HMAC 签名,无需带任何签名头** |
|
||||||
|
| `Authorization: Bearer xxx` | 否 | 不需要 Bearer token |
|
||||||
|
|
||||||
|
### 2.3 Query 参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 | 示例 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| `orgId` | string | 是 | 机构 ID | `LESINGLE888888888` |
|
||||||
|
| `phone` | string | 是 | **登录用户手机号(参与用户级额度计算 + 日志追溯)** | `13800138000` |
|
||||||
|
|
||||||
|
> **⚠️ V2.1 变更**:phone 从"仅日志追溯"升级为"参与额度计算"——它是用户级 Redis 计数器的 key 一部分,必须传**真实登录用户手机号**,不能瞎传。
|
||||||
|
|
||||||
|
### 2.4 完整请求示例
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/quota/user?orgId=LESINGLE888888888&phone=13800138000 HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
**curl**:
|
||||||
|
```bash
|
||||||
|
curl 'http://121.40.20.224:8267/api/v1/quota/user?orgId=LESINGLE888888888&phone=13800138000'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 响应
|
||||||
|
|
||||||
|
### 3.1 统一外层结构(与现有 ApiResponse 对齐)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": { ... }, // 见下方 UserQuotaVO
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 `data` 对象 `UserQuotaVO`(V2.1 用户级闸门版,8 字段)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `orgName` | string | 机构名称(用于显示「XX 幼儿园 · 剩余 N 次」) |
|
||||||
|
| `quotaLimit` | int | **机构总创作配额(A1/A2/A3 共享池)** |
|
||||||
|
| `quotaRemaining` | int | **机构剩余创作次数** |
|
||||||
|
| `userCallLimit` | int | **本用户每周期创作上限**。`0` = 未启用上限(不限制单用户) |
|
||||||
|
| `userCallRemaining` | int | **本用户本周期剩余次数**。`-1` = 不限制;`>=0` = 实际剩余 |
|
||||||
|
| `canCreate` | boolean | **决策位**:机构剩 > 0 **且** (用户不限 **或** 用户剩 > 0) 才 `true` |
|
||||||
|
| `reason` | string\|null | 不可创作时的实际情况;`canCreate=true` 时为 `null` |
|
||||||
|
| `summary` | string | **服务端拼好的用户友好文案**,前端直接 Toast 即可 |
|
||||||
|
|
||||||
|
> **⚠️ 范围说明**:本接口额度仅反映 A1/A2/A3 主创作.
|
||||||
|
> A6/A7 提取重建额度(`a5_user_call_limit`)、视频生成额度等不在此接口范围.
|
||||||
|
|
||||||
|
### 3.3 `canCreate=false` 的四类原因(按闸门顺序)
|
||||||
|
|
||||||
|
| 闸门 | `reason` 示例 | 含义 |
|
||||||
|
|------|---------------|------|
|
||||||
|
| ① 机构存在 | `"机构不存在"` | orgId 在数据库中查不到 |
|
||||||
|
| ② 机构授权 | `"机构「XX 幼儿园」未授权"` | 机构记录存在但未开通授权 |
|
||||||
|
| ③ 机构总额度 | `"机构创作额度已耗尽(共 1000 次)"` | quotaA - quotaAUsed ≤ 0 |
|
||||||
|
| ④ 用户级额度 | `"本用户本周期创作额度已用完(共 5 次/周期)"` | user_call_limit > 0 且本用户已达上限 |
|
||||||
|
|
||||||
|
### 3.4 响应示例
|
||||||
|
|
||||||
|
#### ✅ 通过场景 A:用户级有上限
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"orgName": "XX 幼儿园",
|
||||||
|
"quotaLimit": 1000,
|
||||||
|
"quotaRemaining": 832,
|
||||||
|
"userCallLimit": 5,
|
||||||
|
"userCallRemaining": 3,
|
||||||
|
"canCreate": true,
|
||||||
|
"reason": null,
|
||||||
|
"summary": "本人本周期剩余 3 次(机构总剩 832 次)"
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ✅ 通过场景 B:用户级不限制(userCallLimit=0)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"orgName": "XX 幼儿园",
|
||||||
|
"quotaLimit": 1000,
|
||||||
|
"quotaRemaining": 832,
|
||||||
|
"userCallLimit": 0,
|
||||||
|
"userCallRemaining": -1,
|
||||||
|
"canCreate": true,
|
||||||
|
"reason": null,
|
||||||
|
"summary": "可继续创作,剩余 832 次"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 拒绝:机构不存在
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"orgName": null,
|
||||||
|
"quotaLimit": 0,
|
||||||
|
"quotaRemaining": 0,
|
||||||
|
"userCallLimit": 0,
|
||||||
|
"userCallRemaining": -1,
|
||||||
|
"canCreate": false,
|
||||||
|
"reason": "机构不存在",
|
||||||
|
"summary": "机构「LESINGLE999999999」不存在,请联系管理员核对机构 ID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 拒绝:机构未授权
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"orgName": "XX 幼儿园",
|
||||||
|
"quotaLimit": 0,
|
||||||
|
"quotaRemaining": 0,
|
||||||
|
"userCallLimit": 0,
|
||||||
|
"userCallRemaining": -1,
|
||||||
|
"canCreate": false,
|
||||||
|
"reason": "机构「XX 幼儿园」未授权",
|
||||||
|
"summary": "机构尚未授权,请联系管理员开通服务"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 拒绝:机构额度已耗尽
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"orgName": "XX 幼儿园",
|
||||||
|
"quotaLimit": 1000,
|
||||||
|
"quotaRemaining": 0,
|
||||||
|
"userCallLimit": 5,
|
||||||
|
"userCallRemaining": 2,
|
||||||
|
"canCreate": false,
|
||||||
|
"reason": "机构创作额度已耗尽(共 1000 次)",
|
||||||
|
"summary": "机构创作额度已用完,请联系管理员补充额度"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 拒绝:本用户本周期额度已用完(V2.1 新增场景)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"orgName": "XX 幼儿园",
|
||||||
|
"quotaLimit": 1000,
|
||||||
|
"quotaRemaining": 832,
|
||||||
|
"userCallLimit": 5,
|
||||||
|
"userCallRemaining": 0,
|
||||||
|
"canCreate": false,
|
||||||
|
"reason": "本用户本周期创作额度已用完(共 5 次/周期)",
|
||||||
|
"summary": "您本周期创作次数已用完(共 5 次),请等待下个周期或联系管理员"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 这正是 V2.0 不返用户级字段时**会被坑的场景**:机构剩 832 次,用户却已用完个人 5 次额度。V2.0 错给 `canCreate=true`,V2.1 正确给 `false`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 前端使用建议(推荐流程)
|
||||||
|
|
||||||
|
> ### 🟢 核心原则:**通过 / 失败 两种场景都必须把 `vo.summary` 主动展示给用户**
|
||||||
|
>
|
||||||
|
> `summary` 是服务端拼好的**唯一信息源**,前端不要自己拼词、不要丢弃。
|
||||||
|
> - **失败**:Toast / Dialog 拦截,不放行
|
||||||
|
> - **通过**:同样 Toast / 顶部条幅 / 入口角标提示"还剩 X 次",让用户**心里有数**
|
||||||
|
>
|
||||||
|
> 不要把通过场景的 `summary` 默默吞掉——用户点"+"号是个**心智决策点**,必须告诉他"你还剩 3 次",不然下次进来才发现没了,体验断崖。
|
||||||
|
|
||||||
|
```
|
||||||
|
点击 "+" 创作入口
|
||||||
|
↓
|
||||||
|
弹"额度查询中..."转圈对话框(防止用户连点)
|
||||||
|
↓
|
||||||
|
GET /api/v1/quota/user?orgId=xxx&phone=登录手机号
|
||||||
|
↓
|
||||||
|
设置 3s 超时兜底(超时直接放行,后端创作提交时还会强校验)
|
||||||
|
↓
|
||||||
|
┌─ canCreate=true → 关闭转圈, Toast(vo.summary) 提示"剩余 N 次", 进画画页
|
||||||
|
├─ canCreate=false → 关闭转圈, Dialog(vo.summary) 拦截, 不进入
|
||||||
|
├─ HTTP 异常 → 关闭转圈, 放行(兜底体验, 创作提交时再次校验)
|
||||||
|
└─ 超时 3s → 关闭转圈, 放行(同上)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.1 通过场景 — summary 直接 Toast(推荐)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 不管通过还是失败, summary 都展示给用户
|
||||||
|
// 通过场景的 summary 长这样: "本人本周期剩余 3 次(机构总剩 832 次)"
|
||||||
|
// 或 "可继续创作,剩余 832 次"(用户级不限时)
|
||||||
|
if (vo.canCreate) {
|
||||||
|
Toast.show(vo.summary, { duration: 2000 }) // 2s 轻提示, 不阻塞进入创作页
|
||||||
|
navigateToSketchBook()
|
||||||
|
} else {
|
||||||
|
Dialog.alert(vo.summary, { title: '无法创作', confirmText: '我知道了' })
|
||||||
|
// 不进入创作页
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 主入口角标 — 持续提示剩余次数(推荐 UI 增强)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// "+号" 创作入口旁边挂一个角标 / 副标题, 让用户随时看到额度
|
||||||
|
if (vo.canCreate) {
|
||||||
|
if (vo.userCallLimit > 0 && vo.userCallRemaining >= 0) {
|
||||||
|
// 有用户级上限:显示双数字, 让用户清楚个人剩余
|
||||||
|
showBadge(`本人剩 ${vo.userCallRemaining}/${vo.userCallLimit}`)
|
||||||
|
showSubtitle(`${vo.orgName} · 机构剩 ${vo.quotaRemaining} 次`)
|
||||||
|
} else if (vo.quotaLimit > 0) {
|
||||||
|
showBadge(`剩余 ${vo.quotaRemaining} 次`)
|
||||||
|
showSubtitle(vo.orgName)
|
||||||
|
} else {
|
||||||
|
showSubtitle(vo.orgName) // 没启用 quotaLimit 时不显示数字
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度条颜色阈值(用户级优先, 因为用户感知最强)
|
||||||
|
let ratio
|
||||||
|
if (vo.userCallLimit > 0) {
|
||||||
|
ratio = vo.userCallRemaining / vo.userCallLimit
|
||||||
|
} else if (vo.quotaLimit > 0) {
|
||||||
|
ratio = vo.quotaRemaining / vo.quotaLimit
|
||||||
|
} else {
|
||||||
|
ratio = 1
|
||||||
|
}
|
||||||
|
if (ratio > 0.3) color = 'green'
|
||||||
|
else if (ratio > 0.1) color = 'orange'
|
||||||
|
else color = 'red'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 反例 — 不要这么做 ❌
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ❌ 反例 1:通过场景默默放行, 用户不知道还剩多少
|
||||||
|
if (vo.canCreate) {
|
||||||
|
navigateToSketchBook() // 没提示, 体验断崖
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 反例 2:自己拼词, 丢掉服务端 summary
|
||||||
|
if (vo.canCreate) {
|
||||||
|
Toast.show(`还剩 ${vo.quotaRemaining} 次`) // 漏掉用户级数字, 信息不全
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 反例 3:只在 canCreate=false 时提示, 通过场景静默
|
||||||
|
// → 用户连续创作 4 次后, 第 5 次突然被拒, 完全没有预警
|
||||||
|
|
||||||
|
// ❌ 反例 4:拿 reason 字段当用户文案展示
|
||||||
|
if (!vo.canCreate) {
|
||||||
|
Dialog.alert(vo.reason) // 错!reason 是系统视角("本用户...")
|
||||||
|
// 应该用 summary("您的本周期创作次数已用完...")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 ⭐ `reason` vs `summary` 分工速查表(前端必读)
|
||||||
|
|
||||||
|
> **底层逻辑**:服务端给两个字段是**故意分工**,**前端必须用对**:
|
||||||
|
> - `reason` = **系统视角**,给运维 / 客服 / 日志 / 埋点用,**不要给用户看**
|
||||||
|
> - `summary` = **用户视角**("您"开头的人称话术),**直接展示给用户**,前端**不要拼词、不要加工**
|
||||||
|
|
||||||
|
| 场景 | `canCreate` | `reason`(系统视角,日志用) | `summary`(用户视角,直接展示) | 前端展示方式 |
|
||||||
|
|------|-------------|---------------------------|-----------------------------|------------|
|
||||||
|
| ✅ 通过 + 用户级有上限 | true | `null` | "本人本周期剩余 3 次(机构总剩 832 次)" | Toast 2s + 入口角标 |
|
||||||
|
| ✅ 通过 + 用户级不限 | true | `null` | "可继续创作,剩余 832 次" | Toast 2s + 入口角标 |
|
||||||
|
| ❌ 机构不存在 | false | "机构不存在" | "机构「LESINGLE999」不存在,请联系管理员核对机构 ID" | Dialog 拦截 |
|
||||||
|
| ❌ 机构未授权 | false | "机构「XX 幼儿园」未授权" | "机构尚未授权,请联系管理员开通服务" | Dialog 拦截 |
|
||||||
|
| ❌ 机构额度耗尽 | false | "机构创作额度已耗尽(共 1000 次)" | "机构创作额度已用完,请联系管理员补充额度" | Dialog 拦截 |
|
||||||
|
| ❌ **本用户额度用完** | false | "本用户本周期创作额度已用完(共 5 次/周期)" | **"您本周期创作次数已用完(共 5 次),请等待下个周期或联系管理员"** | Dialog 拦截 |
|
||||||
|
|
||||||
|
**关键差异举例**(V2.1 新增的用户级耗尽场景):
|
||||||
|
|
||||||
|
```
|
||||||
|
reason : "本用户本周期创作额度已用完(共 5 次/周期)" ← 系统视角, 给客服查问题用
|
||||||
|
summary : "您本周期创作次数已用完(共 5 次),请等待下个周期或联系管理员" ← 用户视角, 直接 Dialog
|
||||||
|
```
|
||||||
|
|
||||||
|
**Owner 意识**:summary 文案、用词、人称、引导动作("请联系管理员"/"请等待下个周期")**全部在服务端拼好**,前端只负责"原文展示"。如果你想改文案 → 提需求让后端改 → 后端在 `QuotaQueryController.buildPassSummary` / 各闸门分支统一改 → 多端自动同步,**单一信息源原则**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 注意事项
|
||||||
|
|
||||||
|
1. **不要带 HMAC 签名头**:服务端 `HmacAuthenticationFilter` 已对此路径加白名单,带签名头不会报错但浪费计算。
|
||||||
|
2. **额度仅含 A1/A2/A3**:A6 角色提取、A7 角色重建、视频生成有各自的额度池,本接口**不反映**这些。
|
||||||
|
3. **三层兜底语义**:机构不存在 / 机构未授权 / 机构空 / 用户空 任一不过即 `canCreate=false`。
|
||||||
|
4. **phone 必须是真实登录用户**:它是用户级 Redis key 的一部分,传错号码 = 查到错的人的额度。
|
||||||
|
5. **没有真正的鉴权**:任何人知道 orgId+phone 即可查到额度数字。如果未来要收紧,可改成需要 Bearer token,**接口语义不变**。
|
||||||
|
6. **不修改任何状态**:纯查询接口,重复调用幂等。
|
||||||
|
7. **创作提交时仍会强校验**:本接口返回 `canCreate=true` 不代表 100% 能创建成功,A1/A2/A3 提交时会再次原子扣减额度(机构 + 用户两层都校验)。前端不应依赖此接口做"扣减预占"。
|
||||||
|
8. **`userCallLimit=0` 含义**:服务端兜底未启用单用户上限——即同机构不限制单用户次数(早期未配置时默认值)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 错误码
|
||||||
|
|
||||||
|
| HTTP | code | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 200 | 200 | 业务正常(即使 `canCreate=false` 也是 200) |
|
||||||
|
| 400 | 40001 | orgId 或 phone 缺失 |
|
||||||
|
| 500 | 50000 | 服务端异常(数据库不可达) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. V2.0 → V2.1 迁移说明(前端适配)
|
||||||
|
|
||||||
|
| V2.0 字段 | V2.1 字段 | 处理 |
|
||||||
|
|-----------|-----------|------|
|
||||||
|
| `orgName` | `orgName` | 不变 ✓ |
|
||||||
|
| `quotaLimit` | `quotaLimit` | 不变 ✓ |
|
||||||
|
| `quotaRemaining` | `quotaRemaining` | 不变 ✓ |
|
||||||
|
| — | `userCallLimit` | **新增**:本用户每周期创作上限(0=不限) |
|
||||||
|
| — | `userCallRemaining` | **新增**:本用户本周期剩余次数(-1=不限) |
|
||||||
|
| `canCreate` | `canCreate` | 决策位语义升级(双闸:机构 ✓ + 用户 ✓) |
|
||||||
|
| `reason` | `reason` | 新增 1 类原因「本用户本周期创作额度已用完」 |
|
||||||
|
| `summary` | `summary` | 通过场景拼词升级,含本人剩余次数 |
|
||||||
|
|
||||||
|
**Android 端代码免改条件**:原代码只用 `isCanCreate()` + `getSummary()` 两字段,可平滑兼容。
|
||||||
|
**Android 端推荐适配**:补显示 `getUserCallRemaining()`,让用户清楚个人剩余次数。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 服务端校验链路(运维参考)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/quota/user?orgId=X&phone=P
|
||||||
|
↓
|
||||||
|
QuotaQueryController.getUserQuota
|
||||||
|
├─ 闸 1: orgMapper.selectOne(orgId) → 机构存在?
|
||||||
|
├─ 闸 2: org.authorized == 1 → 已授权?
|
||||||
|
├─ 闸 3: org.quotaA - quotaAUsed > 0 → 机构有额度?
|
||||||
|
└─ 闸 4: QuotaService.getUserCallRemaining(orgId, phone, periodId)
|
||||||
|
├─ resolveUserCallLimit(orgId) // org.user_call_limit 优先, 兜底 t_config
|
||||||
|
└─ Redis GET aicreate:quota:user:{orgId}:{phone}:{periodId}
|
||||||
|
→ limit - used = remaining
|
||||||
|
```
|
||||||
|
|
||||||
|
**用户级 Redis Key 格式**:`aicreate:quota:user:{orgId}:{phone}:{periodId}`
|
||||||
|
**TTL**:跟随活动周期,不会无限增长
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 变更历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 变更 |
|
||||||
|
|------|------|------|
|
||||||
|
| V1.0 | 2026-04-18 | 首次发布(12 字段) |
|
||||||
|
| V2.0 | 2026-04-18 | 简化为 6 字段,只保留 A1/A2/A3 主创作额度;reason 改自然语言 |
|
||||||
|
| V2.1 | 2026-04-18 | 补回 `userCallLimit`/`userCallRemaining`(V2.0 误删),canCreate 升级双闸,对齐后端真实校验链路 |
|
||||||
@ -94,6 +94,15 @@ const fetchActivities = async () => {
|
|||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
keyword: keyword.value || undefined,
|
keyword: keyword.value || undefined,
|
||||||
})
|
})
|
||||||
|
// 全库仅 1 个公开活动时直接进入详情,避免多一层列表
|
||||||
|
if (res.total === 1 && res.list.length === 1) {
|
||||||
|
await router.replace({
|
||||||
|
name: "PublicActivityDetail",
|
||||||
|
params: { id: String(res.list[0].id) },
|
||||||
|
query: { soleActivity: "1" },
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
activities.value = res.list
|
activities.value = res.list
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<span>{{ activity.contestName?.charAt(0) }}</span>
|
<span>{{ activity.contestName?.charAt(0) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-overlay">
|
<div class="hero-overlay">
|
||||||
<a-button shape="round" size="small" @click="$router.back()" class="back-btn">
|
<a-button v-if="showHeroBack" shape="round" size="small" @click="$router.back()" class="back-btn">
|
||||||
<arrow-left-outlined /> 返回
|
<arrow-left-outlined /> 返回
|
||||||
</a-button>
|
</a-button>
|
||||||
<div class="hero-badge">{{ stageLabel }}</div>
|
<div class="hero-badge">{{ stageLabel }}</div>
|
||||||
@ -337,6 +337,10 @@ import dayjs from 'dayjs'
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
/** 活动大厅仅 1 条时列表页 replace 会带 soleActivity=1,无列表可回,不显示返回 */
|
||||||
|
const showHeroBack = computed(() => route.query.soleActivity !== "1")
|
||||||
|
|
||||||
const activity = ref<PublicActivityDetail | null>(null)
|
const activity = ref<PublicActivityDetail | null>(null)
|
||||||
const activeTab = ref('info')
|
const activeTab = ref('info')
|
||||||
const children = ref<any[]>([])
|
const children = ref<any[]>([])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user