From 7ef9e85e2d694163c6b43f5ec163094e54099114 Mon Sep 17 00:00:00 2001 From: jianghao <332515344@qq.com> Date: Thu, 26 Feb 2026 10:12:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=94=A8=E6=88=B7=E6=9C=8D=E5=8A=A1):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=85=E4=BA=8B=E6=9C=8D=E5=8A=A1Feign?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=86=94=E6=96=AD=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加StoryServiceClientFallback和StoryServiceClientFallbackFactory实现类,用于在故事服务不可用时提供默认值 配置feign.circuitbreaker.enabled=true启用熔断功能 --- .../service/impl/AnalyticsServiceImpl.java | 184 ++++++++++++++++++ .../user/feign/StoryServiceClient.java | 2 +- .../feign/StoryServiceClientFallback.java | 31 +++ .../StoryServiceClientFallbackFactory.java | 26 +++ .../src/main/resources/application.properties | 13 +- 5 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 timeline-story-service/src/main/java/com/timeline/story/service/impl/AnalyticsServiceImpl.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallback.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallbackFactory.java diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/impl/AnalyticsServiceImpl.java b/timeline-story-service/src/main/java/com/timeline/story/service/impl/AnalyticsServiceImpl.java new file mode 100644 index 0000000..c850b94 --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/service/impl/AnalyticsServiceImpl.java @@ -0,0 +1,184 @@ +package com.timeline.story.service.impl; + +import com.timeline.story.dao.StoryMapper; +import com.timeline.story.dao.StoryItemMapper; +import com.timeline.story.service.AnalyticsService; +import com.timeline.story.vo.StoryDetailVo; +import com.timeline.story.vo.StoryItemVo; +import com.timeline.story.vo.TimelineAnalyticsVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * AnalyticsServiceImpl - 数据分析服务实现类 + */ +@Slf4j +@Service +public class AnalyticsServiceImpl implements AnalyticsService { + + @Autowired + private StoryMapper storyMapper; + + @Autowired + private StoryItemMapper storyItemMapper; + + @Override + public TimelineAnalyticsVo getOverallStats(String userId) { + log.info("获取用户总体统计: userId={}", userId); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + + // 基础统计数据 - 使用现有方法查询 + List stories = storyMapper.selectByOwnerId(userId); + vo.setTotalStories((long) (stories != null ? stories.size() : 0)); + + // 统计所有故事中的时刻数量 + long totalMoments = 0; + long imageCount = 0; + long videoCount = 0; + + if (stories != null) { + for (StoryDetailVo story : stories) { + Map params = new HashMap<>(); + params.put("storyInstanceId", story.getInstanceId()); + List items = storyItemMapper.selectStoryItemByStoryInstanceId(params); + if (items != null) { + totalMoments += items.size(); + for (StoryItemVo item : items) { + // 根据 video_url 判断类型:有 video_url 是视频,否则是图片 + if (item.getVideoUrl() != null && !item.getVideoUrl().isEmpty()) { + videoCount++; + } else { + imageCount++; + } + } + } + } + } + + vo.setTotalMoments(totalMoments); + vo.setTotalMedia(totalMoments); + vo.setImageCount(imageCount); + vo.setVideoCount(videoCount); + vo.setCollaborationCount(0L); + vo.setTotalComments(0L); + vo.setTotalLikes(0L); + vo.setTotalFavorites(0L); + vo.setConsecutiveDays(0); + vo.setMaxConsecutiveDays(0); + + return vo; + } + + @Override + public TimelineAnalyticsVo getStoryStats(String storyInstanceId) { + log.info("获取故事统计: storyInstanceId={}", storyInstanceId); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + // TODO: 实现故事统计逻辑 + vo.setTotalMoments(0L); + vo.setTotalMedia(0L); + return vo; + } + + @Override + public TimelineAnalyticsVo getMonthlyTrend(String userId, int year) { + log.info("获取月度趋势: userId={}, year={}", userId, year); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + List monthlyTrend = new ArrayList<>(); + + // 生成12个月的默认数据 + for (int month = 1; month <= 12; month++) { + TimelineAnalyticsVo.MonthlyStats stats = new TimelineAnalyticsVo.MonthlyStats(); + stats.setMonth(String.format("%d-%02d", year, month)); + stats.setCount(0L); + stats.setMediaCount(0L); + monthlyTrend.add(stats); + } + + vo.setMonthlyTrend(monthlyTrend); + return vo; + } + + @Override + public TimelineAnalyticsVo getTopLocations(String userId, int limit) { + log.info("获取热门地点: userId={}, limit={}", userId, limit); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + vo.setTopLocations(new ArrayList<>()); + return vo; + } + + @Override + public TimelineAnalyticsVo getTopTags(String userId, int limit) { + log.info("获取热门标签: userId={}, limit={}", userId, limit); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + vo.setTopTags(new ArrayList<>()); + return vo; + } + + @Override + public TimelineAnalyticsVo.YearlyReport generateYearlyReport(String userId, int year) { + log.info("生成年度报告: userId={}, year={}", userId, year); + TimelineAnalyticsVo.YearlyReport report = new TimelineAnalyticsVo.YearlyReport(); + report.setYear(year); + report.setTotalMoments(0L); + report.setTotalMedia(0L); + report.setMostActiveMonth("-"); + report.setMostActiveDay("-"); + report.setTopLocation("-"); + report.setTopTag("-"); + report.setMonthlyBreakdown(new ArrayList<>()); + return report; + } + + @Override + public TimelineAnalyticsVo getActivityStats(String userId) { + log.info("获取活跃度统计: userId={}", userId); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + vo.setConsecutiveDays(0); + vo.setMaxConsecutiveDays(0); + vo.setLastActiveDate(LocalDate.now().format(DateTimeFormatter.ISO_DATE)); + return vo; + } + + @Override + public int calculateConsecutiveDays(String userId) { + log.info("计算连续记录天数: userId={}", userId); + return 0; + } + + @Override + public TimelineAnalyticsVo getTimeDistribution(String userId) { + log.info("获取时间分布: userId={}", userId); + TimelineAnalyticsVo vo = new TimelineAnalyticsVo(); + + // 初始化周分布 + Map weeklyDistribution = new HashMap<>(); + for (int i = 1; i <= 7; i++) { + weeklyDistribution.put(i, 0L); + } + vo.setWeeklyDistribution(weeklyDistribution); + + // 初始化小时分布 + Map hourlyDistribution = new HashMap<>(); + for (int i = 0; i < 24; i++) { + hourlyDistribution.put(i, 0L); + } + vo.setHourlyDistribution(hourlyDistribution); + + return vo; + } + + @Override + public byte[] exportStats(String userId, String format) { + log.info("导出统计数据: userId={}, format={}", userId, format); + // 返回空数据 + return new byte[0]; + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClient.java b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClient.java index 000c360..47835f4 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClient.java +++ b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClient.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; * Story Service Feign Client * 用于调用 timeline-story-service 的接口 */ -@FeignClient(name = "timeline-story-service", path = "/story") +@FeignClient(name = "timeline-story-service", path = "/story", fallbackFactory = StoryServiceClientFallbackFactory.class) public interface StoryServiceClient { /** diff --git a/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallback.java b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallback.java new file mode 100644 index 0000000..a79e327 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallback.java @@ -0,0 +1,31 @@ +package com.timeline.user.feign; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * StoryServiceClient Fallback + * 当 story-service 不可用时返回默认值 + */ +@Slf4j +@Component +public class StoryServiceClientFallback implements StoryServiceClient { + + @Override + public Long countStoriesByUserId(String userId) { + log.warn("StoryService 不可用,返回默认故事数量 0, userId={}", userId); + return 0L; + } + + @Override + public Long countItemsByUserId(String userId) { + log.warn("StoryService 不可用,返回默认照片/视频数量 0, userId={}", userId); + return 0L; + } + + @Override + public Long getTotalStorageByUserId(String userId) { + log.warn("StoryService 不可用,返回默认存储使用量 0, userId={}", userId); + return 0L; + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallbackFactory.java b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallbackFactory.java new file mode 100644 index 0000000..8ff6b57 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/feign/StoryServiceClientFallbackFactory.java @@ -0,0 +1,26 @@ +package com.timeline.user.feign; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * StoryServiceClient Fallback Factory + * 用于创建 Fallback 实例并记录异常原因 + */ +@Slf4j +@Component +public class StoryServiceClientFallbackFactory implements FallbackFactory { + + private final StoryServiceClientFallback fallback; + + public StoryServiceClientFallbackFactory(StoryServiceClientFallback fallback) { + this.fallback = fallback; + } + + @Override + public StoryServiceClient create(Throwable cause) { + log.error("StoryServiceClient 调用失败,使用 Fallback 处理", cause); + return fallback; + } +} diff --git a/timeline-user-service/src/main/resources/application.properties b/timeline-user-service/src/main/resources/application.properties index b9ac998..071bd18 100644 --- a/timeline-user-service/src/main/resources/application.properties +++ b/timeline-user-service/src/main/resources/application.properties @@ -53,10 +53,13 @@ server.servlet.encoding.force=true spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=2 # 5 minutes -spring.datasource.hikari.idle-timeout=300000 +spring.datasource.hikari.idle-timeout=300000 # 30 minutes, below MySQL wait_timeout -spring.datasource.hikari.max-lifetime=1800000 +spring.datasource.hikari.max-lifetime=1800000 # wait up to 30s for a connection -spring.datasource.hikari.connection-timeout=30000 -spring.datasource.hikari.keepalive-time=0 -spring.datasource.hikari.validation-timeout=5000 \ No newline at end of file +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.keepalive-time=0 +spring.datasource.hikari.validation-timeout=5000 + +# Feign Client Configuration +feign.circuitbreaker.enabled=true \ No newline at end of file