实体类
package com.yu.cloudpicturebackend.model.entity;import lombok.Data;import java.io.Serializable;@Data
public class DailyWord implements Serializable {private static final long serialVersionUID = 2539265557480005492L;private String content;private String author;
}
服务层 Service
package com.yu.cloudpicturebackend.service;import com.yu.cloudpicturebackend.model.entity.DailyWord;import java.util.List;public interface ThirdPartyService {/*** 获取每日一句*/List<DailyWord> getDailyWord(Integer count);}
服务层 ServiceImpl
package com.yu.cloudpicturebackend.service.impl;import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.yu.cloudpicturebackend.exception.BusinessException;
import com.yu.cloudpicturebackend.exception.ErrorCode;
import com.yu.cloudpicturebackend.model.entity.DailyWord;
import com.yu.cloudpicturebackend.service.ThirdPartyService;
import com.yu.cloudpicturebackend.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;@Service
@Slf4j
public class ThirdPartyServiceImpl implements ThirdPartyService {@Resourceprivate RestTemplate restTemplate;@Resourceprivate RedisUtil redisUtil;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Value("${thirdParty.appId}")private String appId;@Value("${thirdParty.appSecret}")private String appSecret;@Value("${thirdParty.dailyWordUrl}")private String baseUrl;private static final String REDIS_KEY = "daily_word";@Overridepublic List<DailyWord> getDailyWord(Integer count) {try {// 参数验证if (count < 1 || count > 20) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "count参数范围应为1-20");}// 使用 UriComponentsBuilder 安全构建URLString url = UriComponentsBuilder.fromHttpUrl(baseUrl).queryParam("count", count).queryParam("app_id", appId).queryParam("app_secret", appSecret).toUriString();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<String> entity = new HttpEntity<>(headers);// 优先从缓存获取List<DailyWord> dailyWords = getFromRedis(REDIS_KEY);if (dailyWords != null) {log.info("从 redis 缓存获取每日一句数据");return dailyWords;}// 发送请求ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);log.info("每日一句接口调用成功,响应体: {}", response.getBody());// 解析响应体String body = response.getBody();JSONObject jsonObject = JSONUtil.parseObj(body);int code = (int) jsonObject.get("code");JSONArray jsonArray = jsonObject.getJSONArray("data");dailyWords = JSONUtil.toList(jsonArray, DailyWord.class);List<DailyWord> words = dailyWords.stream().map(dailyWord -> {DailyWord word = new DailyWord();word.setContent(dailyWord.getContent());word.setAuthor(StrUtil.isBlank(dailyWord.getAuthor()) ? "佚名" : dailyWord.getAuthor());return word;}).collect(Collectors.toList());// 数据存入 redissaveWithDailyExpire(REDIS_KEY, JSONUtil.toJsonStr(words));if (code == 1) {return words;} else {throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);}} catch (Exception e) {log.error("调用每日一句接口失败: ", e);throw new BusinessException(ErrorCode.SYSTEM_ERROR, "请求第三方接口失败: " + e.getMessage());}}/*** 从 redis 获取数据** @param key* @return*/private List<DailyWord> getFromRedis(String key) {String jsonStr = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(jsonStr)) {return JSONUtil.toList(jsonStr, DailyWord.class);}return null;}/*** 存储数据并设置过期时间为当天24点*/private void saveWithDailyExpire(String key, String value) {try {stringRedisTemplate.opsForValue().set(key, value);// 设置过期时间为当天24点long expireSeconds = getSecondsUntilMidnight();stringRedisTemplate.expire(key, expireSeconds, TimeUnit.SECONDS);log.info("存储数据成功, key: {}, 过期时间: {}秒后", key, expireSeconds);} catch (Exception e) {log.error("存储数据失败, key: {}, 错误: {}", key, e.getMessage());}}/*** 计算当前时间到当天24点的秒数*/private long getSecondsUntilMidnight() {LocalDateTime now = LocalDateTime.now();LocalDateTime midnight = now.toLocalDate().atTime(23, 59, 59).plusSeconds(1); // 次日00:00:00return Duration.between(now, midnight).getSeconds();}}
接口层 Controller
package com.yu.cloudpicturebackend.controller;import com.yu.cloudpicturebackend.common.ResultUtils;
import com.yu.cloudpicturebackend.exception.BaseResponse;
import com.yu.cloudpicturebackend.model.entity.DailyWord;
import com.yu.cloudpicturebackend.service.ThirdPartyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;@RestController
@RequestMapping("/thirdParty")
@Api(tags = "第三方接口")
@Slf4j
public class ThirdPartyController {@Resourceprivate ThirdPartyService thirdPartyService;/*** 获取每日一句*/@GetMapping("/dailyWord")@ApiOperation("每日一句")public BaseResponse<?> getDailyWord(@RequestParam(defaultValue = "1") Integer count) {List<DailyWord> dailyWords = thirdPartyService.getDailyWord(count);return ResultUtils.success(dailyWords);}
}