feat(interaction): 支持游客互动与登录合并
This commit is contained in:
1 parent
2fe24b9db7
commit
4b0fbd73d3
41 files changed
+1207
-90
No files matched your search
+1
@@ -20,6 +20,7 @@ public class UserFavorite {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String deviceId;
|
||||
private Long dramaId;
|
||||
private Integer sort;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
+7
@@ -17,4 +17,11 @@ public interface UserFavoriteMapper extends BaseMapper<UserFavorite> {
|
||||
*/
|
||||
@Delete("DELETE FROM user_favorite WHERE user_id = #{userId} AND drama_id = #{dramaId}")
|
||||
int physicalDelete(@Param("userId") Long userId, @Param("dramaId") Long dramaId);
|
||||
|
||||
@Delete("DELETE FROM user_favorite WHERE user_id IS NULL AND device_id = #{deviceId} "
|
||||
+ "AND drama_id = #{dramaId}")
|
||||
int physicalDeleteDevice(@Param("deviceId") String deviceId, @Param("dramaId") Long dramaId);
|
||||
|
||||
@Delete("DELETE FROM user_favorite WHERE id = #{id}")
|
||||
int physicalDeleteById(@Param("id") Long id);
|
||||
}
|
||||
+77
-7
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.interaction.InteractionIdentity;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.enums.ContentStatus;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
@@ -37,21 +38,28 @@ public class FavoriteService {
|
||||
/** 返回切换后的状态:true 表示已收藏。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean toggle(Long userId, Long dramaId) {
|
||||
return toggle(new InteractionIdentity(userId, ""), dramaId);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean toggle(InteractionIdentity identity, Long dramaId) {
|
||||
dramaService.getPublished(dramaId);
|
||||
UserFavorite existing = favoriteMapper.selectOne(Wrappers.<UserFavorite>lambdaQuery()
|
||||
.eq(UserFavorite::getUserId, userId)
|
||||
UserFavorite existing = favoriteMapper.selectOne(identityQuery(identity)
|
||||
.eq(UserFavorite::getDramaId, dramaId)
|
||||
.last("limit 1"));
|
||||
if (existing != null) {
|
||||
// 必须物理删除:唯一索引 (user_id, drama_id) 会挡住"取消后再收藏"
|
||||
int removed = favoriteMapper.physicalDelete(userId, dramaId);
|
||||
int removed = identity.authenticated()
|
||||
? favoriteMapper.physicalDelete(identity.userId(), dramaId)
|
||||
: favoriteMapper.physicalDeleteDevice(identity.deviceId(), dramaId);
|
||||
if (removed > 0) {
|
||||
dramaService.adjustFavoriteCount(dramaId, -1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
UserFavorite favorite = new UserFavorite();
|
||||
favorite.setUserId(userId);
|
||||
favorite.setUserId(identity.userId());
|
||||
favorite.setDeviceId(identity.authenticated() ? "" : identity.deviceId());
|
||||
favorite.setDramaId(dramaId);
|
||||
favorite.setSort(0);
|
||||
favoriteMapper.insert(favorite);
|
||||
@@ -68,7 +76,13 @@ public class FavoriteService {
|
||||
|
||||
/** 批量判断用户是否已收藏,一次查询覆盖多部剧。 */
|
||||
public Set<Long> favoriteDramaIds(Long userId, Collection<Long> dramaIds) {
|
||||
if (userId == null || dramaIds == null || dramaIds.isEmpty()) {
|
||||
return favoriteDramaIds(userId, "", dramaIds);
|
||||
}
|
||||
|
||||
/** 登录用户优先;匿名状态按设备号查询。 */
|
||||
public Set<Long> favoriteDramaIds(Long userId, String deviceId,
|
||||
Collection<Long> dramaIds) {
|
||||
if (dramaIds == null || dramaIds.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<Long> normalized = new HashSet<>();
|
||||
@@ -80,14 +94,62 @@ public class FavoriteService {
|
||||
if (normalized.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
return favoriteMapper.selectList(Wrappers.<UserFavorite>lambdaQuery()
|
||||
.eq(UserFavorite::getUserId, userId)
|
||||
if (userId == null && (deviceId == null || deviceId.isBlank())) {
|
||||
return Set.of();
|
||||
}
|
||||
return favoriteMapper.selectList(identityQuery(new InteractionIdentity(userId, deviceId))
|
||||
.in(UserFavorite::getDramaId, normalized))
|
||||
.stream()
|
||||
.map(UserFavorite::getDramaId)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
}
|
||||
|
||||
/** 登录时把设备收藏归并到账号;重复收藏删除并回退剧级计数。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MergeResult mergeDeviceIntoUser(Long userId, String deviceId) {
|
||||
if (userId == null || deviceId == null || deviceId.isBlank()) {
|
||||
return new MergeResult(0, 0);
|
||||
}
|
||||
List<UserFavorite> deviceFavorites = favoriteMapper.selectList(
|
||||
Wrappers.<UserFavorite>lambdaQuery()
|
||||
.isNull(UserFavorite::getUserId)
|
||||
.eq(UserFavorite::getDeviceId, deviceId));
|
||||
int migrated = 0;
|
||||
int duplicates = 0;
|
||||
for (UserFavorite deviceFavorite : deviceFavorites) {
|
||||
UserFavorite existing = favoriteMapper.selectOne(
|
||||
Wrappers.<UserFavorite>lambdaQuery()
|
||||
.eq(UserFavorite::getUserId, userId)
|
||||
.eq(UserFavorite::getDramaId, deviceFavorite.getDramaId())
|
||||
.last("limit 1"));
|
||||
if (existing != null) {
|
||||
if (favoriteMapper.physicalDeleteById(deviceFavorite.getId()) > 0) {
|
||||
dramaService.adjustFavoriteCount(deviceFavorite.getDramaId(), -1);
|
||||
}
|
||||
duplicates++;
|
||||
continue;
|
||||
}
|
||||
deviceFavorite.setUserId(userId);
|
||||
favoriteMapper.updateById(deviceFavorite);
|
||||
migrated++;
|
||||
}
|
||||
return new MergeResult(migrated, duplicates);
|
||||
}
|
||||
|
||||
private com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<UserFavorite>
|
||||
identityQuery(InteractionIdentity identity) {
|
||||
if (identity.authenticated()) {
|
||||
return Wrappers.<UserFavorite>lambdaQuery()
|
||||
.eq(UserFavorite::getUserId, identity.userId());
|
||||
}
|
||||
return Wrappers.<UserFavorite>lambdaQuery()
|
||||
.isNull(UserFavorite::getUserId)
|
||||
.eq(UserFavorite::getDeviceId, identity.deviceId());
|
||||
}
|
||||
|
||||
public record MergeResult(int migrated, int duplicates) {
|
||||
}
|
||||
|
||||
/** 分页查询收藏短剧,保持收藏时间与 dramaId 稳定排序。 */
|
||||
public PageResult<Drama> page(Long userId, int pageIndex, int pageSize) {
|
||||
int safeIndex = Math.max(pageIndex, 1);
|
||||
@@ -130,6 +192,14 @@ public class FavoriteService {
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeDevice(String deviceId, Long dramaId) {
|
||||
int removed = favoriteMapper.physicalDeleteDevice(deviceId, dramaId);
|
||||
if (removed > 0) {
|
||||
dramaService.adjustFavoriteCount(dramaId, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeBatch(Long userId, List<Long> dramaIds) {
|
||||
if (dramaIds == null || dramaIds.isEmpty()) {
|
||||
|
||||
+6
@@ -2,6 +2,8 @@
|
||||
|
||||
package com.fruitshorts.playback.service;
|
||||
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
@@ -57,6 +59,10 @@ public class PlaybackService {
|
||||
|
||||
boolean free = episode.getIsFree() != null && episode.getIsFree() == 1;
|
||||
boolean entitled = userId != null && entitlementPort.isUnlocked(userId, drama.getId(), episode.getId());
|
||||
if (!free && !entitled && userId == null) {
|
||||
// 锁定集正片必须登录;即使暂时没有片源,也不能把游客引到匿名取票分支。
|
||||
throw new ServiceException(ErrorCode.UNAUTHORIZED, "登录后才能解锁并观看该集");
|
||||
}
|
||||
String source = playableSource(episode);
|
||||
|
||||
PlayRespVO vo = new PlayRespVO();
|
||||
|
||||
+3
@@ -11,4 +11,7 @@ public class FavoriteToggleReqVO {
|
||||
|
||||
@NotNull(message = "缺少 dramaId")
|
||||
private Long dramaId;
|
||||
|
||||
/** 兼容旧调用;正常客户端通过 X-Device-Id 下发。 */
|
||||
private String deviceId;
|
||||
}
|
||||
Reference in new issue
Block a user