chore: 撤回旧收藏设计文档,改用独立收藏表方案
This commit is contained in:
@@ -1,218 +0,0 @@
|
|||||||
---
|
|
||||||
author: AI Assistant
|
|
||||||
created_at: 2026-07-19
|
|
||||||
purpose: 小程序收藏功能从 localStorage 走真实 API,在现有剧本/事件表增加 is_favorite 字段
|
|
||||||
---
|
|
||||||
|
|
||||||
# 小程序收藏功能真实 API 化设计
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
当前小程序的「收藏」功能(剧本收藏、人生事件收藏)完全基于前端 localStorage:
|
|
||||||
- 剧本:`uni.getStorageSync('script_favorites')` → 对象字典 `{ id: true/false }`
|
|
||||||
- 人生事件:`uni.getStorageSync('event_favorite_${id}')` → 单条布尔
|
|
||||||
|
|
||||||
后端没有真实的剧本收藏 API,人生事件收藏接口 `/lifeEvent/favorite-placeholder` 是假的(echo 请求参数 + `placeholder: true`)。
|
|
||||||
|
|
||||||
本次修复:在现有 `e_epic_script` 表和 `t_life_event` 表上新增 `is_favorite` 字段,新增真实 API,前端从 localStorage 切换到 API。
|
|
||||||
|
|
||||||
## 现状问题
|
|
||||||
|
|
||||||
### 前端
|
|
||||||
|
|
||||||
| 文件 | 问题 |
|
|
||||||
|---|---|
|
|
||||||
| `ScriptLibraryView.vue:159` | `localFavorites = ref(uni.getStorageSync('script_favorites') \|\| {})` |
|
|
||||||
| `ScriptLibraryView.vue:319-369` | `toggleFavorite` / `deleteScriptFromFavorites` 只写 localStorage |
|
|
||||||
| `ScriptLibraryView.vue:244` | `isFavorite` 从 `script.isFavorite \|\| localFavorites.value[id]` 读取 |
|
|
||||||
| `life-event/detail.vue:142` | 从 `current_life_event` localStorage 读整条事件数据 |
|
|
||||||
| `life-event/detail.vue:144` | `isFavorite` 从 `event_favorite_${id}` localStorage 读 |
|
|
||||||
| `life-event/detail.vue:315-316` | toggle 写/删 `event_favorite_${id}` localStorage |
|
|
||||||
|
|
||||||
### 后端
|
|
||||||
|
|
||||||
| 文件 | 问题 |
|
|
||||||
|---|---|
|
|
||||||
| `LifeEventController.java:153-163` | `/lifeEvent/favorite-placeholder` 是空壳,返回 echo + `placeholder: true` |
|
|
||||||
| 无剧本收藏 Controller | `/epicScript` 下没有任何收藏端点 |
|
|
||||||
|
|
||||||
## 设计方案
|
|
||||||
|
|
||||||
### 存储方案(已确认)
|
|
||||||
|
|
||||||
在现有表新增 `is_favorite` 字段:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TABLE e_epic_script ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
|
|
||||||
ALTER TABLE t_life_event ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
|
|
||||||
```
|
|
||||||
|
|
||||||
**选择这个方案的理由**:
|
|
||||||
1. 不新增表,改动最小
|
|
||||||
2. `is_favorite` 是当前用户维度的标记;用户只操作自己的剧本/事件,不需要 user_id 维度
|
|
||||||
3. 现有 `listAll` / `page` 接口返回全量字段,新增 `isFavorite` 字段后前端可直接读取,不需要额外的"收藏夹"接口
|
|
||||||
4. 后续如需"按用户维度"收藏(如管理员视角),再迁移到独立表
|
|
||||||
|
|
||||||
### 后端改动
|
|
||||||
|
|
||||||
#### 1. 实体类
|
|
||||||
|
|
||||||
- `EpicScript.java` 新增:`private Boolean isFavorite;`
|
|
||||||
- `LifeEvent.java` 新增:`private Boolean isFavorite;`
|
|
||||||
|
|
||||||
MyBatis-Plus 自动映射下划线 `is_favorite` 到驼峰 `isFavorite`。
|
|
||||||
|
|
||||||
#### 2. Controller API
|
|
||||||
|
|
||||||
**剧本**(`EpicScriptController`):
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /epicScript/favorite
|
|
||||||
入参:FavoriteRequest { Long id; Boolean favorite; }
|
|
||||||
出参:Result<Void>
|
|
||||||
逻辑:
|
|
||||||
1. 通过 id 查询剧本
|
|
||||||
2. 若不存在 → 抛业务异常("剧本不存在")
|
|
||||||
3. update is_favorite = favorite
|
|
||||||
4. return Result.ok()
|
|
||||||
```
|
|
||||||
|
|
||||||
**人生事件**(`LifeEventController`):
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /lifeEvent/favorite
|
|
||||||
入参:FavoriteRequest { Long id; Boolean favorite; }
|
|
||||||
出参:Result<Void>
|
|
||||||
逻辑:
|
|
||||||
1. 通过 id 查询事件
|
|
||||||
2. 若不存在 → 抛业务异常
|
|
||||||
3. update is_favorite = favorite
|
|
||||||
4. return Result.ok()
|
|
||||||
```
|
|
||||||
|
|
||||||
删除现有 `/lifeEvent/favorite-placeholder` 接口。
|
|
||||||
|
|
||||||
#### 3. Service 层
|
|
||||||
|
|
||||||
```java
|
|
||||||
// EpicScriptService
|
|
||||||
void setFavorite(Long id, Boolean favorite);
|
|
||||||
|
|
||||||
// LifeEventService
|
|
||||||
void setFavorite(Long id, Boolean favorite);
|
|
||||||
```
|
|
||||||
|
|
||||||
实现:`lambdaUpdate().set(EpicScript::getIsFavorite, favorite).eq(EpicScript::getId, id).update()`
|
|
||||||
|
|
||||||
#### 4. DTO
|
|
||||||
|
|
||||||
新增公共请求对象 `FavoriteRequest`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class FavoriteRequest {
|
|
||||||
@NotNull
|
|
||||||
private Long id;
|
|
||||||
@NotNull
|
|
||||||
private Boolean favorite;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
放在 `common` 模块,两个 Controller 都能引用。
|
|
||||||
|
|
||||||
### 前端改动
|
|
||||||
|
|
||||||
#### 1. `stores/app.js`
|
|
||||||
|
|
||||||
新增两个 action:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async toggleScriptFavorite(id, favorite) {
|
|
||||||
await epicScriptService.setFavorite(id, favorite);
|
|
||||||
const script = this.scripts.find(s => String(s.id) === String(id));
|
|
||||||
if (script) script.isFavorite = favorite;
|
|
||||||
},
|
|
||||||
|
|
||||||
async toggleEventFavorite(id, favorite) {
|
|
||||||
await lifeEventService.setFavorite(id, favorite);
|
|
||||||
// 事件列表如果是 store 内状态,同步更新
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. `services/epicScript.js`
|
|
||||||
|
|
||||||
新增:
|
|
||||||
|
|
||||||
```js
|
|
||||||
setFavorite: (id, favorite) => post('/epicScript/favorite', { id: String(id), favorite })
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. `services/lifeEvent.js`
|
|
||||||
|
|
||||||
修改现有 `favoriteEvent` 实现:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// 之前:post('/lifeEvent/favorite-placeholder', ...)
|
|
||||||
// 之后:post('/lifeEvent/favorite', { id, favorite })
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. `ScriptLibraryView.vue`
|
|
||||||
|
|
||||||
- 删除 `const localFavorites = ref(uni.getStorageSync('script_favorites') || {})`
|
|
||||||
- 删除 `toggleFavorite` / `deleteScriptFromFavorites` 的 localStorage 写/删
|
|
||||||
- `isFavorite` 计算改为:`Boolean(script.isFavorite)`
|
|
||||||
- `toggleFavorite` 改为调 `store.toggleScriptFavorite(id, !script.isFavorite)`
|
|
||||||
|
|
||||||
#### 5. `life-event/detail.vue`
|
|
||||||
|
|
||||||
- 删除 `uni.getStorageSync('current_life_event')` 的回退读取(这是"把数据存到用户端"的另一处违规,在 sub-project B 一并清理)
|
|
||||||
- 删除 `uni.getStorageSync('event_favorite_${id}')` 读取
|
|
||||||
- `isFavorite` 改为 `computed(() => Boolean(displayEvent.value?.isFavorite))`
|
|
||||||
- toggle 改为调 `store.toggleEventFavorite(id, !isFavorite.value)` 或 `lifeEventService.favoriteEvent`
|
|
||||||
|
|
||||||
## 数据流
|
|
||||||
|
|
||||||
```
|
|
||||||
列表页 ScriptLibraryView
|
|
||||||
└─ fetchScripts() (GET /epicScript/listAll)
|
|
||||||
└─ 后端查 e_epic_script 含 is_favorite 字段
|
|
||||||
└─ 前端渲染 isFavorite
|
|
||||||
|
|
||||||
用户点收藏
|
|
||||||
└─ store.toggleScriptFavorite(id, true/false)
|
|
||||||
└─ POST /epicScript/favorite { id, favorite }
|
|
||||||
└─ 后端 update is_favorite
|
|
||||||
└─ 本地 state 同步,UI 响应式更新
|
|
||||||
|
|
||||||
详情页 life-event/detail
|
|
||||||
└─ fetchLifeEventDetail(id) (GET /lifeEvent/detail)
|
|
||||||
└─ 后端查 t_life_event 含 is_favorite
|
|
||||||
└─ 前端渲染 isFavorite
|
|
||||||
```
|
|
||||||
|
|
||||||
## 不影响范围
|
|
||||||
|
|
||||||
- 列表渲染、筛选、排序逻辑不变,只换数据源(localStorage → API 字段)
|
|
||||||
- 详情页展示逻辑不变(数据源变化在 sub-project B 清理)
|
|
||||||
- 其他页面的剧本/事件展示(如首页推荐)自动受益于新的 `isFavorite` 字段
|
|
||||||
|
|
||||||
## 数据库迁移
|
|
||||||
|
|
||||||
新增迁移 SQL 文件 `server/src/main/resources/db/migration/V{timestamp}__add_is_favorite.sql`:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TABLE e_epic_script ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
|
|
||||||
ALTER TABLE t_life_event ADD COLUMN is_favorite TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否收藏:0-否 1-是';
|
|
||||||
```
|
|
||||||
|
|
||||||
如果项目不用 Flyway,则手动执行或写入项目约定的 SQL 目录。
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
|
|
||||||
- [ ] 剧本收藏:点击心形图标 → 调用 `POST /epicScript/favorite` → 后端字段更新 → 刷新页面后仍保持状态
|
|
||||||
- [ ] 人生事件收藏:点击收藏 → 调用 `POST /lifeEvent/favorite` → 同上
|
|
||||||
- [ ] 取消收藏:再次点击 → 同上
|
|
||||||
- [ ] 列表页 `isFavorite` 来自 API,不再依赖 localStorage
|
|
||||||
- [ ] 详情页 `isFavorite` 来自 API,不再依赖 localStorage
|
|
||||||
- [ ] 浏览器 Console 无报错
|
|
||||||
- [ ] 后端 `mvn clean install` 编译通过
|
|
||||||
- [ ] 通过 `deploy.py backend` 部署到服务器后,在服务器环境验证
|
|
||||||
Reference in New Issue
Block a user