Skip to content

Commit 0725e39

Browse files
committed
feat(BangumiService): add user-specific episode watch status and update episode retrieval method
1 parent 82cc1ad commit 0725e39

11 files changed

Lines changed: 289 additions & 6 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.ligg.common.entity;
2+
3+
import com.baomidou.mybatisplus.annotation.IdType;
4+
import com.baomidou.mybatisplus.annotation.TableId;
5+
import com.baomidou.mybatisplus.annotation.TableName;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.time.LocalDateTime;
10+
11+
@Data
12+
@NoArgsConstructor
13+
@TableName("user_episode_watch")
14+
public class UserEpisodeWatchEntity {
15+
16+
@TableId(type = IdType.AUTO)
17+
private Long id;
18+
19+
private Long userId;
20+
21+
private Integer subjectId;
22+
23+
private Integer episodeId;
24+
25+
/**
26+
* 0 未看,1 已看。
27+
*/
28+
private Integer watchStatus;
29+
30+
/**
31+
* 首次标记已看的时间。
32+
*/
33+
private LocalDateTime watchedAt;
34+
35+
private LocalDateTime updateTime;
36+
37+
private LocalDateTime createTime;
38+
}

common/src/main/java/com/ligg/common/thirdparty/bangumi/response/SubjectEpisodesDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ligg.common.thirdparty.bangumi.response;
22

33
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
45
import com.fasterxml.jackson.annotation.JsonProperty;
56
import lombok.Data;
67

@@ -31,5 +32,7 @@ public static class Episode {
3132
private String airdate;
3233
private Integer comment;
3334
private String desc;
35+
@JsonInclude(JsonInclude.Include.NON_NULL)
36+
private Boolean watched;
3437
}
3538
}

flow-client/src/main/java/com/ligg/flowclient/controller/FlowUserController.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import com.ligg.flowclient.annotation.IpEndpointRateLimit;
1212
import com.ligg.flowclient.interceptor.AuthorizationInterceptor;
1313
import com.ligg.flowclient.module.dto.UpdateUserCollectionDto;
14+
import com.ligg.flowclient.module.dto.UpdateEpisodeWatchDto;
1415
import com.ligg.flowclient.module.dto.UpdateUserDto;
1516
import com.ligg.flowclient.module.vo.FlowUserVo;
17+
import com.ligg.flowclient.module.vo.SubjectEpisodeWatchStatusVo;
1618
import com.ligg.common.vo.bangumi.UserCollectionsVo;
1719
import com.ligg.flowclient.module.vo.UserBgmCollectionSyncStatusVo;
1820
import com.ligg.flowclient.service.*;
@@ -36,6 +38,8 @@ public class FlowUserController {
3638

3739
private final JwtTokenService jwtTokenService;
3840

41+
private final UserEpisodeWatchService userEpisodeWatchService;
42+
3943
/**
4044
* 获取当前登录用户信息(需携带 AnimeFlow access_token)。
4145
*/
@@ -104,6 +108,29 @@ public Result<Void> updateCollection(
104108
return Result.success();
105109
}
106110

111+
/**
112+
* 标记当前用户某一集已看/未看。
113+
*/
114+
@PutMapping("/episodes/{episodeId}/watch")
115+
public Result<Void> updateEpisodeWatch(
116+
@RequestAttribute(AuthorizationInterceptor.ACCESS_TOKEN_REQUEST_ATTRIBUTE) String accessToken,
117+
@PathVariable int episodeId,
118+
@Valid @RequestBody UpdateEpisodeWatchDto body) {
119+
userEpisodeWatchService.updateEpisodeWatch(accessToken, episodeId, body);
120+
return Result.success();
121+
}
122+
123+
/**
124+
* 获取当前用户对某部作品的已看剧集列表。
125+
*/
126+
@GetMapping("/subjects/{subjectId}/episodes/watch-status")
127+
public Result<SubjectEpisodeWatchStatusVo> getSubjectEpisodeWatchStatus(
128+
@RequestAttribute(AuthorizationInterceptor.ACCESS_TOKEN_REQUEST_ATTRIBUTE) String accessToken,
129+
@PathVariable int subjectId) {
130+
SubjectEpisodeWatchStatusVo vo = userEpisodeWatchService.getSubjectWatchStatus(accessToken, subjectId);
131+
return Result.success(ResponseCode.SUCCESS, vo);
132+
}
133+
107134
/**
108135
* 提交 Bangumi 收藏同步任务(异步执行,立即返回任务状态)。
109136
* 从 user_oauth 读取 Bangumi token 拉取收藏并写入 user_bgm_collection。

flow-client/src/main/java/com/ligg/flowclient/controller/bangumi/SubjectsController.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,18 @@ public class SubjectsController {
5454
public Result<SubjectEpisodesVo> subjectEpisodes(
5555
@NotNull @PathVariable int subjectId,
5656
@RequestParam(defaultValue = "100") int limit,
57-
@RequestParam(defaultValue = "0") int offset) {
58-
SubjectEpisodesDto dto = bangumiService.getEpisodes(subjectId, limit, offset);
57+
@RequestParam(defaultValue = "0") int offset,
58+
@RequestAttribute(name = AuthorizationInterceptor.ACCESS_TOKEN_REQUEST_ATTRIBUTE, required = false)
59+
String flowAccessToken) {
60+
long userId = 0;
61+
if (StringUtils.hasText(flowAccessToken)) {
62+
try {
63+
userId = jwtTokenService.validateAccessToken(flowAccessToken);
64+
} catch (LoginExpiredException ignored) {
65+
// token 无效时回退匿名访问
66+
}
67+
}
68+
SubjectEpisodesDto dto = bangumiService.getEpisodes(subjectId, limit, offset, userId);
5969
SubjectEpisodesVo vo = new SubjectEpisodesVo();
6070
BeanUtils.copyProperties(dto, vo);
6171
return Result.success(ResponseCode.SUCCESS, vo);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.ligg.flowclient.mapper;
2+
3+
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4+
import com.ligg.common.entity.UserEpisodeWatchEntity;
5+
import org.apache.ibatis.annotations.Mapper;
6+
7+
@Mapper
8+
public interface UserEpisodeWatchMapper extends BaseMapper<UserEpisodeWatchEntity> {
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.ligg.flowclient.module.dto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import lombok.Data;
5+
6+
@Data
7+
public class UpdateEpisodeWatchDto {
8+
9+
@NotNull
10+
private Boolean watched;
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.ligg.flowclient.module.vo;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
import java.util.List;
7+
8+
@Data
9+
@AllArgsConstructor
10+
public class SubjectEpisodeWatchStatusVo {
11+
12+
private Integer subjectId;
13+
14+
private List<Long> watchedEpisodeIds;
15+
}

flow-client/src/main/java/com/ligg/flowclient/service/BangumiService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public interface BangumiService {
1717
/**
1818
* 获取番剧剧集列表
1919
*/
20-
SubjectEpisodesDto getEpisodes(Integer subjectId, int limit, int offset);
20+
SubjectEpisodesDto getEpisodes(Integer subjectId, int limit, int offset, long userId);
2121

2222
/**
2323
* 获取搜索建议
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.ligg.flowclient.service;
2+
3+
import com.ligg.flowclient.module.dto.UpdateEpisodeWatchDto;
4+
import com.ligg.flowclient.module.vo.SubjectEpisodeWatchStatusVo;
5+
6+
import java.util.Set;
7+
8+
public interface UserEpisodeWatchService {
9+
10+
void updateEpisodeWatch(String accessToken, int episodeId, UpdateEpisodeWatchDto dto);
11+
12+
SubjectEpisodeWatchStatusVo getSubjectWatchStatus(String accessToken, int subjectId);
13+
14+
Set<Long> listWatchedEpisodeIds(long userId, int subjectId);
15+
}

flow-client/src/main/java/com/ligg/flowclient/service/impl/BangumiServiceImpl.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public class BangumiServiceImpl implements BangumiService {
5959
private final ObjectMapper objectMapper;
6060
private final ImageBackfillService imageBackfillService;
6161
private final UserBgmCollectionMapper userBgmCollectionMapper;
62+
private final UserEpisodeWatchService userEpisodeWatchService;
6263

6364
/**
6465
* 条目详情
@@ -224,7 +225,7 @@ private static SubjectDetailDto.SubjectInterest getInterest(UserSubjectInterestR
224225
}
225226

226227
@Override
227-
public SubjectEpisodesDto getEpisodes(Integer subjectId, int limit, int offset) {
228+
public SubjectEpisodesDto getEpisodes(Integer subjectId, int limit, int offset, long userId) {
228229
SubjectEpisodesDto dto = new SubjectEpisodesDto();
229230
if (subjectId == null) {
230231
dto.setData(Collections.emptyList());
@@ -245,11 +246,20 @@ public SubjectEpisodesDto getEpisodes(Integer subjectId, int limit, int offset)
245246
IPage<BangumiEpisodeEntity> page = episodeMapper.selectPage(new LimitOffsetPage<>(limit, offset), wrapper);
246247

247248
dto.setTotal((int) page.getTotal());
248-
dto.setData(page.getRecords().stream().map(this::toEpisode).toList());
249+
Set<Long> watchedEpisodeIds = userId > 0
250+
? userEpisodeWatchService.listWatchedEpisodeIds(userId, subjectId)
251+
: Collections.emptySet();
252+
boolean includeWatched = userId > 0;
253+
dto.setData(page.getRecords().stream()
254+
.map(entity -> toEpisode(entity, watchedEpisodeIds, includeWatched))
255+
.toList());
249256
return dto;
250257
}
251258

252-
private SubjectEpisodesDto.Episode toEpisode(BangumiEpisodeEntity entity) {
259+
private SubjectEpisodesDto.Episode toEpisode(
260+
BangumiEpisodeEntity entity,
261+
Set<Long> watchedEpisodeIds,
262+
boolean includeWatched) {
253263
SubjectEpisodesDto.Episode episode = new SubjectEpisodesDto.Episode();
254264
episode.setId(entity.getId().longValue());
255265
episode.setSubjectId(entity.getSubjectId());
@@ -261,6 +271,9 @@ private SubjectEpisodesDto.Episode toEpisode(BangumiEpisodeEntity entity) {
261271
episode.setDuration(entity.getDuration());
262272
episode.setAirdate(entity.getAirdate());
263273
episode.setDesc(entity.getDescription());
274+
if (includeWatched) {
275+
episode.setWatched(watchedEpisodeIds.contains(entity.getId().longValue()));
276+
}
264277
return episode;
265278
}
266279

0 commit comments

Comments
 (0)