feat(用户服务): 添加故事服务Feign客户端熔断机制
All checks were successful
test/timeline-server/pipeline/head This commit looks good

添加StoryServiceClientFallback和StoryServiceClientFallbackFactory实现类,用于在故事服务不可用时提供默认值
配置feign.circuitbreaker.enabled=true启用熔断功能
This commit is contained in:
2026-02-26 10:12:40 +08:00
parent e4a4c227b5
commit 7ef9e85e2d
5 changed files with 250 additions and 6 deletions

View File

@@ -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<StoryDetailVo> 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<String, Object> params = new HashMap<>();
params.put("storyInstanceId", story.getInstanceId());
List<StoryItemVo> 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<TimelineAnalyticsVo.MonthlyStats> 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<Integer, Long> weeklyDistribution = new HashMap<>();
for (int i = 1; i <= 7; i++) {
weeklyDistribution.put(i, 0L);
}
vo.setWeeklyDistribution(weeklyDistribution);
// 初始化小时分布
Map<Integer, Long> 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];
}
}

View File

@@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam;
* Story Service Feign Client * Story Service Feign Client
* 用于调用 timeline-story-service 的接口 * 用于调用 timeline-story-service 的接口
*/ */
@FeignClient(name = "timeline-story-service", path = "/story") @FeignClient(name = "timeline-story-service", path = "/story", fallbackFactory = StoryServiceClientFallbackFactory.class)
public interface StoryServiceClient { public interface StoryServiceClient {
/** /**

View File

@@ -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;
}
}

View File

@@ -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<StoryServiceClient> {
private final StoryServiceClientFallback fallback;
public StoryServiceClientFallbackFactory(StoryServiceClientFallback fallback) {
this.fallback = fallback;
}
@Override
public StoryServiceClient create(Throwable cause) {
log.error("StoryServiceClient 调用失败,使用 Fallback 处理", cause);
return fallback;
}
}

View File

@@ -60,3 +60,6 @@ spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.keepalive-time=0 spring.datasource.hikari.keepalive-time=0
spring.datasource.hikari.validation-timeout=5000 spring.datasource.hikari.validation-timeout=5000
# Feign Client Configuration
feign.circuitbreaker.enabled=true