feat(用户服务): 添加故事服务Feign客户端熔断机制
All checks were successful
test/timeline-server/pipeline/head This commit looks good
All checks were successful
test/timeline-server/pipeline/head This commit looks good
添加StoryServiceClientFallback和StoryServiceClientFallbackFactory实现类,用于在故事服务不可用时提供默认值 配置feign.circuitbreaker.enabled=true启用熔断功能
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -60,3 +60,6 @@ spring.datasource.hikari.max-lifetime=1800000
|
||||
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
|
||||
Reference in New Issue
Block a user