- 实现 AI 起名功能(Kimi API 接入) - 添加用户收藏功能(MySQL 数据库) - 实现海报生成与分享 - 添加音效和触觉反馈 - 配置生产环境部署(WAR 包 + Nginx) - 支持多种起名模式(经典、诗词、自然、现代) - 实现分批加载优化体验
111 lines
3.9 KiB
Markdown
111 lines
3.9 KiB
Markdown
采用**异步并行生成**(Asynchronous Parallel Generation)是目前平衡“高响应速度”与“AI 生成质量”最立竿见影的方案。
|
||
|
||
其核心思路是将原来“一个请求等五个名字”的线性过程,拆分为“多个并发请求同时生成名字”。
|
||
|
||
---
|
||
|
||
## 1. 后端重构:`MiniMaxService.java`
|
||
|
||
我们需要利用 Java 的 `CompletableFuture` 来并发调用 AI 接口。
|
||
|
||
```java
|
||
@Service
|
||
public class MiniMaxService {
|
||
|
||
// 模拟原有的单次生成逻辑
|
||
private NameCard callAiToGenerateSingleName(String keyword, String mode, String surname) {
|
||
// 这里是原有的 MiniMax API 调用逻辑,但只要求它返回 1 个名字
|
||
// ...
|
||
}
|
||
|
||
public List<NameCard> generateNamesAsync(String keyword, String mode, String surname) {
|
||
int count = 5; // 我们需要生成 5 个名字
|
||
List<CompletableFuture<NameCard>> futures = new ArrayList<>();
|
||
|
||
for (int i = 0; i < count; i++) {
|
||
// 为每个名字开启一个异步线程
|
||
CompletableFuture<NameCard> future = CompletableFuture.supplyAsync(() -> {
|
||
return callAiToGenerateSingleName(keyword, mode, surname);
|
||
});
|
||
futures.add(future);
|
||
}
|
||
|
||
// 等待所有线程完成(或部分完成即可返回,提升容错)
|
||
return futures.stream()
|
||
.map(CompletableFuture::join) // 合并结果
|
||
.collect(Collectors.toList());
|
||
}
|
||
}
|
||
```
|
||
|
||
### 为什么这样做更快?
|
||
* **线性模式**:AI 生成 5 个名字的时间 = $T1 + T2 + T3 + T4 + T5$(通常需要 8-12 秒)。
|
||
* **并行模式**:总耗时 = $max(T1, T2, T3, T4, T5)$(通常只需 2-3 秒)。
|
||
|
||
---
|
||
|
||
## 2. 前端感知优化:伪流式展示
|
||
|
||
虽然后端并行生成了结果,但如果我们等 5 个全部完成再返回,前端仍有空窗期。我们可以配合前端做一个“分批次渲染”。
|
||
|
||
### 修改 `index.js` 的 `fetchNames` 逻辑:
|
||
我们可以将请求拆分为两拨:一拨追求“极速响应”(先拿 2 个名字),二拨追求“完整体验”(再拿 3 个)。
|
||
|
||
```javascript
|
||
fetchNames(keyword, mode, surname) {
|
||
// 1. 发起第一波请求:先取 2 个名字,让用户立刻有卡片可以翻
|
||
this.requestNamingApi(keyword, mode, surname, 2).then(firstBatch => {
|
||
this.setData({
|
||
nameList: firstBatch,
|
||
currentIndex: 0
|
||
});
|
||
|
||
// 2. 第一波成功后,静默发起第二波请求:补齐剩下的名字
|
||
this.requestNamingApi(keyword, mode, surname, 3).then(secondBatch => {
|
||
this.setData({
|
||
nameList: [...this.data.nameList, ...secondBatch]
|
||
});
|
||
});
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 仪式感补偿:Loading 文案池
|
||
|
||
为了让异步加载的 1-2 秒不尴尬,在 `home.js` 跳转到 `index.js` 时,利用一个动态的文案池来分散用户注意力。
|
||
|
||
### 在 `index.wxml` 中添加:
|
||
```html
|
||
<view wx:if="{{nameList.length === 0}}" class="loading-container">
|
||
<view class="ink-blob"></view>
|
||
<text class="loading-text">{{loadingQuote}}</text>
|
||
</view>
|
||
```
|
||
|
||
### 在 `index.js` 中添加文案轮换:
|
||
```javascript
|
||
const quotes = [
|
||
"正在翻阅《古今集成》...",
|
||
"字里行间,寻觅归处...",
|
||
"笔墨将就,意境自生...",
|
||
"正在感悟山川意向..."
|
||
];
|
||
|
||
// 在 onLoad 中开启定时器
|
||
this.loadingTimer = setInterval(() => {
|
||
this.setData({
|
||
loadingQuote: quotes[Math.floor(Math.random() * quotes.length)]
|
||
});
|
||
}, 2000);
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ 实施建议
|
||
|
||
1. **后端线程池**:在使用 `CompletableFuture` 时,务必自定义一个 `Executor`(线程池),不要使用默认的 `ForkJoinPool`,防止在高并发下把后端拖垮。
|
||
2. **音律分析延迟**:如果 `ToneAnalysisService` 依然很慢,可以先返回名字和出处,音律评分字段标记为“计算中”,待卡片翻转时再动态获取。
|
||
|
||
**这套“并行生成 + 分批返回”的方案,能将用户的体感等待时间从 10 秒以上直接压缩到 2 秒左右。** |