From 7a35551a7b678a748abaca958464ead22b907bf9 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 18 Sep 2025 18:42:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(video):=20=E6=B7=BB=E5=8A=A0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=9F=A5=E7=9C=8B=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增视频查看权限相关数据结构和接口 - 实现用户视频查看记录的创建和更新逻辑 - 添加视频查看权限的检查和记录功能 -优化分布式环境下的并发控制 --- .../controller/mobile/AppVideoController.java | 72 +++++- .../video/dto/VideoViewPermissionDTO.java | 119 +++++++++ .../pc/video/entity/UserVideoViewEntity.java | 57 +++++ .../repository/UserVideoViewRepository.java | 168 +++++++++++++ .../mobile/VideoViewPermissionService.java | 230 ++++++++++++++++++ 5 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ycwl/basic/model/mobile/video/dto/VideoViewPermissionDTO.java create mode 100644 src/main/java/com/ycwl/basic/model/pc/video/entity/UserVideoViewEntity.java create mode 100644 src/main/java/com/ycwl/basic/repository/UserVideoViewRepository.java create mode 100644 src/main/java/com/ycwl/basic/service/mobile/VideoViewPermissionService.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppVideoController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppVideoController.java index a636e16a..ad02cb55 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppVideoController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppVideoController.java @@ -1,23 +1,93 @@ package com.ycwl.basic.controller.mobile; +import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.model.mobile.video.dto.VideoViewPermissionDTO; import com.ycwl.basic.model.task.req.VideoInfoReq; import com.ycwl.basic.repository.VideoRepository; +import com.ycwl.basic.service.mobile.VideoViewPermissionService; +import com.ycwl.basic.utils.ApiResponse; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Deprecated +@Slf4j @RestController @RequestMapping("/api/mobile/video/v1") public class AppVideoController { + @Autowired private VideoRepository videoRepository; + @Autowired + private VideoViewPermissionService videoViewPermissionService; + @PostMapping("/{videoId}/updateMeta") public void updateMeta(@PathVariable("videoId") Long videoId, @RequestBody VideoInfoReq req) { videoRepository.updateMeta(videoId, req); } + + /** + * 记录用户查看视频并返回权限信息 + * + * @param videoId 视频ID + * @return 查看权限信息 + */ + @PostMapping("/{videoId}/recordView") + public ApiResponse recordView(@PathVariable("videoId") Long videoId) { + try { + String userIdStr = BaseContextHandler.getUserId(); + if (userIdStr == null || userIdStr.isEmpty()) { + log.warn("用户未登录,无法记录查看: videoId={}", videoId); + return ApiResponse.fail("用户未登录"); + } + + Long userId = Long.valueOf(userIdStr); + log.debug("记录用户查看视频: userId={}, videoId={}", userId, videoId); + + VideoViewPermissionDTO permission = videoViewPermissionService.checkAndRecordView(userId, videoId); + return ApiResponse.success(permission); + + } catch (NumberFormatException e) { + log.error("用户ID格式错误: userId={}, videoId={}", BaseContextHandler.getUserId(), videoId, e); + return ApiResponse.fail("用户信息无效"); + } catch (Exception e) { + log.error("记录用户查看视频失败: videoId={}", videoId, e); + return ApiResponse.fail("记录查看失败,请稍后重试"); + } + } + + /** + * 检查用户查看权限(不记录查看次数) + * + * @param videoId 视频ID + * @return 查看权限信息 + */ + @GetMapping("/{videoId}/checkPermission") + public ApiResponse checkPermission(@PathVariable("videoId") Long videoId) { + try { + String userIdStr = BaseContextHandler.getUserId(); + if (userIdStr == null || userIdStr.isEmpty()) { + log.warn("用户未登录,无法查看权限: videoId={}", videoId); + return ApiResponse.fail("用户未登录"); + } + + Long userId = Long.valueOf(userIdStr); + log.debug("检查用户查看权限: userId={}, videoId={}", userId, videoId); + + VideoViewPermissionDTO permission = videoViewPermissionService.checkViewPermission(userId, videoId); + return ApiResponse.success(permission); + + } catch (NumberFormatException e) { + log.error("用户ID格式错误: userId={}, videoId={}", BaseContextHandler.getUserId(), videoId, e); + return ApiResponse.fail("用户信息无效"); + } catch (Exception e) { + log.error("检查用户查看权限失败: videoId={}", videoId, e); + return ApiResponse.fail("权限检查失败,请稍后重试"); + } + } } diff --git a/src/main/java/com/ycwl/basic/model/mobile/video/dto/VideoViewPermissionDTO.java b/src/main/java/com/ycwl/basic/model/mobile/video/dto/VideoViewPermissionDTO.java new file mode 100644 index 00000000..ed34fc54 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/mobile/video/dto/VideoViewPermissionDTO.java @@ -0,0 +1,119 @@ +package com.ycwl.basic.model.mobile.video.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 视频查看权限响应DTO + * 包含用户查看视频的权限信息和限制详情 + */ +@Data +public class VideoViewPermissionDTO { + + /** + * 是否可以查看 + */ + @JsonProperty("canView") + private Boolean canView; + + /** + * 是否无限制查看 + */ + private Boolean isUnlimited; + + /** + * 当前查看次数 + */ + private Integer currentViewCount; + + /** + * 最大查看次数(0表示无限制) + */ + private Integer maxViewCount; + + /** + * 时间限制(秒,0表示无限制) + */ + private Integer timeLimit; + + /** + * 剩余完整查看次数 + */ + private Integer remainingViews; + + /** + * 权限信息描述 + */ + private String message; + + /** + * 是否为限时查看模式 + */ + private Boolean isTimeLimitMode; + + public VideoViewPermissionDTO() { + this.canView = true; + this.isUnlimited = false; + this.currentViewCount = 0; + this.maxViewCount = 0; + this.timeLimit = 0; + this.remainingViews = 0; + this.isTimeLimitMode = false; + } + + /** + * 创建无限制查看的权限对象 + */ + public static VideoViewPermissionDTO createUnlimitedPermission(Integer currentViewCount) { + VideoViewPermissionDTO dto = new VideoViewPermissionDTO(); + dto.setCanView(true); + dto.setIsUnlimited(true); + dto.setCurrentViewCount(currentViewCount); + dto.setMessage("无限制查看"); + return dto; + } + + /** + * 创建完整查看权限对象 + */ + public static VideoViewPermissionDTO createFullViewPermission(Integer currentViewCount, Integer maxViewCount) { + VideoViewPermissionDTO dto = new VideoViewPermissionDTO(); + dto.setCanView(true); + dto.setIsUnlimited(false); + dto.setCurrentViewCount(currentViewCount); + dto.setMaxViewCount(maxViewCount); + dto.setRemainingViews(Math.max(0, maxViewCount - currentViewCount)); + dto.setMessage("完整查看模式,剩余" + dto.getRemainingViews() + "次"); + return dto; + } + + /** + * 创建限时查看权限对象 + */ + public static VideoViewPermissionDTO createTimeLimitPermission(Integer currentViewCount, Integer maxViewCount, Integer timeLimit) { + VideoViewPermissionDTO dto = new VideoViewPermissionDTO(); + dto.setCanView(true); + dto.setIsUnlimited(false); + dto.setIsTimeLimitMode(true); + dto.setCurrentViewCount(currentViewCount); + dto.setMaxViewCount(maxViewCount); + dto.setTimeLimit(timeLimit); + dto.setRemainingViews(0); + dto.setMessage("限时查看模式,每次可查看" + timeLimit + "秒"); + return dto; + } + + /** + * 创建禁止查看权限对象 + */ + public static VideoViewPermissionDTO createNoPermission(Integer currentViewCount, Integer maxViewCount) { + VideoViewPermissionDTO dto = new VideoViewPermissionDTO(); + dto.setCanView(false); + dto.setIsUnlimited(false); + dto.setCurrentViewCount(currentViewCount); + dto.setMaxViewCount(maxViewCount); + dto.setRemainingViews(0); + dto.setMessage("已达到查看次数限制,无法继续观看"); + return dto; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/model/pc/video/entity/UserVideoViewEntity.java b/src/main/java/com/ycwl/basic/model/pc/video/entity/UserVideoViewEntity.java new file mode 100644 index 00000000..c03eb87e --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/video/entity/UserVideoViewEntity.java @@ -0,0 +1,57 @@ +package com.ycwl.basic.model.pc.video.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 用户视频查看记录实体 + * 记录用户查看vlog视频的次数和时间 + */ +@Data +@TableName("user_video_view_record") +public class UserVideoViewEntity { + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 视频ID + */ + private Long videoId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 完整查看次数 + */ + private Integer viewCount; + + /** + * 最后查看时间 + */ + private Date lastViewTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/repository/UserVideoViewRepository.java b/src/main/java/com/ycwl/basic/repository/UserVideoViewRepository.java new file mode 100644 index 00000000..e82df2fa --- /dev/null +++ b/src/main/java/com/ycwl/basic/repository/UserVideoViewRepository.java @@ -0,0 +1,168 @@ +package com.ycwl.basic.repository; + +import com.ycwl.basic.model.pc.video.entity.UserVideoViewEntity; +import com.ycwl.basic.utils.JacksonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * 用户视频查看记录Repository + * 仅使用Redis存储查看记录 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class UserVideoViewRepository { + + private final RedisTemplate redisTemplate; + + private static final String USER_VIDEO_VIEW_CACHE_KEY = "user_video_view:%s:%s"; + private static final String VIEW_RECORD_LOCK_KEY = "view_record_lock:%s:%s"; + private static final int CACHE_EXPIRE_HOURS = 72; // 3天过期 + private static final int LOCK_EXPIRE_SECONDS = 30; + + /** + * 获取用户视频查看记录 + * + * @param userId 用户ID + * @param videoId 视频ID + * @return 查看记录 + */ + public UserVideoViewEntity getViewRecord(Long userId, Long videoId) { + String cacheKey = String.format(USER_VIDEO_VIEW_CACHE_KEY, userId, videoId); + + if (redisTemplate.hasKey(cacheKey)) { + String cached = redisTemplate.opsForValue().get(cacheKey); + if (cached != null) { + return JacksonUtil.parseObject(cached, UserVideoViewEntity.class); + } + } + + return null; + } + + /** + * 创建查看记录 + * + * @param userId 用户ID + * @param videoId 视频ID + * @param scenicId 景区ID + * @return 查看记录 + */ + public UserVideoViewEntity createViewRecord(Long userId, Long videoId, Long scenicId) { + UserVideoViewEntity entity = new UserVideoViewEntity(); + entity.setUserId(userId); + entity.setVideoId(videoId); + entity.setScenicId(scenicId); + entity.setViewCount(1); + entity.setLastViewTime(new Date()); + entity.setCreateTime(new Date()); + entity.setUpdateTime(new Date()); + + String cacheKey = String.format(USER_VIDEO_VIEW_CACHE_KEY, userId, videoId); + redisTemplate.opsForValue().set(cacheKey, JacksonUtil.toJSONString(entity), + CACHE_EXPIRE_HOURS, TimeUnit.HOURS); + + log.debug("创建用户视频查看记录到Redis: userId={}, videoId={}, scenicId={}", userId, videoId, scenicId); + return entity; + } + + /** + * 更新查看次数 + * + * @param userId 用户ID + * @param videoId 视频ID + * @param viewCount 新的查看次数 + * @return 是否更新成功 + */ + public boolean updateViewCount(Long userId, Long videoId, Integer viewCount) { + UserVideoViewEntity entity = getViewRecord(userId, videoId); + if (entity != null) { + entity.setViewCount(viewCount); + entity.setLastViewTime(new Date()); + entity.setUpdateTime(new Date()); + + String cacheKey = String.format(USER_VIDEO_VIEW_CACHE_KEY, userId, videoId); + redisTemplate.opsForValue().set(cacheKey, JacksonUtil.toJSONString(entity), + CACHE_EXPIRE_HOURS, TimeUnit.HOURS); + + log.debug("更新用户视频查看次数到Redis: userId={}, videoId={}, viewCount={}", userId, videoId, viewCount); + return true; + } + return false; + } + + /** + * 增加查看次数(原子操作) + * + * @param userId 用户ID + * @param videoId 视频ID + * @return 是否更新成功 + */ + public boolean incrementViewCount(Long userId, Long videoId) { + UserVideoViewEntity entity = getViewRecord(userId, videoId); + if (entity != null) { + entity.setViewCount(entity.getViewCount() + 1); + entity.setLastViewTime(new Date()); + entity.setUpdateTime(new Date()); + + String cacheKey = String.format(USER_VIDEO_VIEW_CACHE_KEY, userId, videoId); + redisTemplate.opsForValue().set(cacheKey, JacksonUtil.toJSONString(entity), + CACHE_EXPIRE_HOURS, TimeUnit.HOURS); + + log.debug("增加用户视频查看次数到Redis: userId={}, videoId={}, newCount={}", + userId, videoId, entity.getViewCount()); + return true; + } + return false; + } + + /** + * 使用Redis分布式锁执行查看记录操作 + * + * @param userId 用户ID + * @param videoId 视频ID + * @param operation 操作函数 + * @return 操作结果 + */ + public T executeWithLock(Long userId, Long videoId, java.util.function.Supplier operation) { + String lockKey = String.format(VIEW_RECORD_LOCK_KEY, userId, videoId); + + try { + // 尝试获取锁 + Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", + LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS); + + if (Boolean.TRUE.equals(lockAcquired)) { + try { + return operation.get(); + } finally { + // 释放锁 + redisTemplate.delete(lockKey); + } + } else { + log.warn("获取查看记录锁失败: userId={}, videoId={}", userId, videoId); + return null; + } + } catch (Exception e) { + log.error("执行查看记录操作失败: userId={}, videoId={}", userId, videoId, e); + return null; + } + } + + /** + * 清除缓存 + * + * @param userId 用户ID + * @param videoId 视频ID + */ + public void clearCache(Long userId, Long videoId) { + String cacheKey = String.format(USER_VIDEO_VIEW_CACHE_KEY, userId, videoId); + redisTemplate.delete(cacheKey); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/service/mobile/VideoViewPermissionService.java b/src/main/java/com/ycwl/basic/service/mobile/VideoViewPermissionService.java new file mode 100644 index 00000000..eb9c211e --- /dev/null +++ b/src/main/java/com/ycwl/basic/service/mobile/VideoViewPermissionService.java @@ -0,0 +1,230 @@ +package com.ycwl.basic.service.mobile; + +import com.ycwl.basic.biz.OrderBiz; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; +import com.ycwl.basic.model.mobile.order.IsBuyRespVO; +import com.ycwl.basic.model.mobile.video.dto.VideoViewPermissionDTO; +import com.ycwl.basic.model.pc.video.entity.UserVideoViewEntity; +import com.ycwl.basic.model.pc.video.entity.VideoEntity; +import com.ycwl.basic.repository.ScenicRepository; +import com.ycwl.basic.repository.UserVideoViewRepository; +import com.ycwl.basic.repository.VideoRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * 视频查看权限服务 + * 处理用户查看vlog视频的权限控制和记录 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class VideoViewPermissionService { + + private final UserVideoViewRepository userVideoViewRepository; + private final VideoRepository videoRepository; + private final ScenicRepository scenicRepository; + private final OrderBiz orderBiz; + + /** + * 检查并记录用户查看视频 + * + * @param userId 用户ID + * @param videoId 视频ID + * @return 查看权限信息 + */ + public VideoViewPermissionDTO checkAndRecordView(Long userId, Long videoId) { + try { + // 获取视频信息 + VideoEntity video = videoRepository.getVideo(videoId); + if (video == null) { + log.warn("视频不存在: videoId={}", videoId); + return createErrorPermission("视频不存在"); + } + + Long scenicId = video.getScenicId(); + if (scenicId == null) { + log.warn("视频缺少景区信息: videoId={}", videoId); + return createErrorPermission("视频信息不完整"); + } + + // 检查用户是否已购买 + IsBuyRespVO buy = orderBiz.isBuy(userId, scenicId, 0, videoId); + if (buy != null && (buy.isBuy() || buy.isFree())) { + // 已购买,不限制查看 + log.debug("用户已购买视频,无查看限制: userId={}, videoId={}", userId, videoId); + return VideoViewPermissionDTO.createUnlimitedPermission(0); + } + + // 使用分布式锁执行查看记录操作 + VideoViewPermissionDTO result = userVideoViewRepository.executeWithLock(userId, videoId, () -> { + try { + // 获取景区配置 + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + + // 解析配置参数 + Integer vlogViewLimit = scenicConfig.getInteger("vlog_view_limit", 0); + Integer vlogTimeLimit = scenicConfig.getInteger("vlog_time_limit", 0); + + // 获取用户查看记录 + UserVideoViewEntity viewRecord = userVideoViewRepository.getViewRecord(userId, videoId); + int currentViewCount = 0; + + if (viewRecord == null) { + // 首次查看,创建记录 + viewRecord = userVideoViewRepository.createViewRecord(userId, videoId, scenicId); + currentViewCount = 1; + } else { + // 增加查看次数 + currentViewCount = viewRecord.getViewCount() + 1; + userVideoViewRepository.updateViewCount(userId, videoId, currentViewCount); + } + + // 根据配置判断权限 + return calculatePermission(currentViewCount, vlogViewLimit, vlogTimeLimit); + + } catch (Exception e) { + log.error("检查视频查看权限失败: userId={}, videoId={}", userId, videoId, e); + return createErrorPermission("系统异常,请稍后重试"); + } + }); + + return result != null ? result : createErrorPermission("获取权限信息失败"); + + } catch (Exception e) { + log.error("检查并记录视频查看失败: userId={}, videoId={}", userId, videoId, e); + return createErrorPermission("系统异常,请稍后重试"); + } + } + + /** + * 仅检查用户查看权限,不记录查看次数 + * + * @param userId 用户ID + * @param videoId 视频ID + * @return 查看权限信息 + */ + public VideoViewPermissionDTO checkViewPermission(Long userId, Long videoId) { + try { + // 获取视频信息 + VideoEntity video = videoRepository.getVideo(videoId); + if (video == null) { + return createErrorPermission("视频不存在"); + } + + Long scenicId = video.getScenicId(); + if (scenicId == null) { + return createErrorPermission("视频信息不完整"); + } + + // 检查用户是否已购买 + IsBuyRespVO buy = orderBiz.isBuy(userId, scenicId, 0, videoId); + if (buy != null && (buy.isBuy() || buy.isFree())) { + // 已购买,不限制查看 + log.debug("用户已购买视频,无查看限制: userId={}, videoId={}", userId, videoId); + return VideoViewPermissionDTO.createUnlimitedPermission(0); + } + + // 获取景区配置 + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + + // 解析配置参数 + Integer vlogViewLimit = scenicConfig.getInteger("vlog_view_limit", 0); + Integer vlogTimeLimit = scenicConfig.getInteger("vlog_time_limit", 0); + + // 获取用户查看记录 + UserVideoViewEntity viewRecord = userVideoViewRepository.getViewRecord(userId, videoId); + int currentViewCount = viewRecord != null ? viewRecord.getViewCount() : 0; + + // 根据配置判断权限 + return calculatePermission(currentViewCount, vlogViewLimit, vlogTimeLimit); + + } catch (Exception e) { + log.error("检查视频查看权限失败: userId={}, videoId={}", userId, videoId, e); + return createErrorPermission("系统异常,请稍后重试"); + } + } + + /** + * 根据配置计算查看权限 + * + * @param currentViewCount 当前查看次数 + * @param vlogViewLimit 视频查看次数限制 + * @param vlogTimeLimit 时间限制(秒) + * @return 查看权限信息 + */ + private VideoViewPermissionDTO calculatePermission(int currentViewCount, Integer vlogViewLimit, Integer vlogTimeLimit) { + // 1. 两个值都为0或负数则为不限制 + if ((vlogViewLimit == null || vlogViewLimit <= 0) && (vlogTimeLimit == null || vlogTimeLimit <= 0)) { + return VideoViewPermissionDTO.createUnlimitedPermission(currentViewCount); + } + + // 2. vlog_view_limit为0则仅通过vlog_time_limit限制 + if (vlogViewLimit == null || vlogViewLimit <= 0) { + if (vlogTimeLimit != null && vlogTimeLimit > 0) { + return VideoViewPermissionDTO.createTimeLimitPermission(currentViewCount, 0, vlogTimeLimit); + } else { + return VideoViewPermissionDTO.createUnlimitedPermission(currentViewCount); + } + } + + // 3. vlog_view_limit不为0,判断是否超过次数限制 + if (currentViewCount <= vlogViewLimit) { + // 未超过次数限制,可完整查看 + return VideoViewPermissionDTO.createFullViewPermission(currentViewCount, vlogViewLimit); + } else { + // 已超过次数限制 + if (vlogTimeLimit == null || vlogTimeLimit <= 0) { + // 4. vlog_time_limit为0就不允许查看了 + return VideoViewPermissionDTO.createNoPermission(currentViewCount, vlogViewLimit); + } else { + // 通过时间限制查看 + return VideoViewPermissionDTO.createTimeLimitPermission(currentViewCount, vlogViewLimit, vlogTimeLimit); + } + } + } + + /** + * 解析配置值 + * + * @param config 配置Map + * @param key 配置键 + * @param defaultValue 默认值 + * @return 解析后的值 + */ + private Integer parseConfigValue(Map config, String key, Integer defaultValue) { + if (config == null || !config.containsKey(key)) { + return defaultValue; + } + + Object value = config.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof String) { + try { + return Integer.valueOf((String) value); + } catch (NumberFormatException e) { + log.warn("配置值解析失败: key={}, value={}, 使用默认值: {}", key, value, defaultValue); + return defaultValue; + } + } + + return defaultValue; + } + + /** + * 创建错误权限对象 + * + * @param message 错误消息 + * @return 错误权限对象 + */ + private VideoViewPermissionDTO createErrorPermission(String message) { + VideoViewPermissionDTO dto = new VideoViewPermissionDTO(); + dto.setCanView(false); + dto.setMessage(message); + return dto; + } +} \ No newline at end of file