关于Pexels api的补充

前言

https://www.codefather.cn/post/1889226006084644865

上一篇讲了如何通过Pexels api实现简易版的“以图搜图”功能,虽然大体上没有啥问题,但是有一些代码细节还是需要进调整的,包括有一些细心的朋友应该已经发现了。

我们Pexels的 API_KEY 以及百度的 APP_ID 和 APP_KEY 都还嵌入在代码中,这样提交代码的时候也会一并提交到Github远程仓库中,所以我们需要进行作出一些改动,可以直接把他移入到application-local.yml中,和我们之前阿里云api的API_KEY处理方式即可。

开始编码

提取

首先我们需要把 Pexels API 和 Baidu API 分别提取到 application-local.yml中。

YAML
复制代码
# Pexels API pexels: apiKey: 你的apikey # Baidu API baidu: baiduAppId: 你的百度apiid baiduApiKey: 你的百度apikey

修改代码

修改 PexelsImageSearch 类,替换 apikey ,baiduAppId ,baiduApiKey。

Java
复制代码
@Value("${pexels.apiKey}") private String apiKey; private static final String ENDPOINT = "https://api.pexels.com/v1/search"; @Value("${baidu.baiduAppId}") private String baiduAppId; @Value("${baidu.baiduApiKey}") private String baiduApiKey;

测试

重启代码进行测试

发现前端页面以图搜图功能直接无法加载数据,回到idea查看,发现直接报错了。

问题排查

然后我开始进行debug,看看哪里的值为null了。

测试发现

apikey ,baiduAppId ,baiduApiKey 这三个值都是null,初步猜测好像并没有从配置文件读取成功,或者是在代码中途运行过程中发生了一些问题导致@value注解失效了呢。

于是我没有把精力全部放在PexelsImageSearch 这类上了,于是找到了他的上游,也就是ImageSearchApiFacade,因为在以图搜图的controller中是通过ImageSearchApiFacade调用searchImage方法然后进一步去关联到

PexelsImageSearch这个类才去调用Pexels api的,果然在searchImage方法中有了重大的发现。

Java
复制代码
/** * 搜索图片 * * @param query 图片关键字(中文) * @return 图片搜索结果列表 */ public static List<ImageSearchResult> searchImage(String query) { // 先进行中文转英文 PexelsImageSearch imageSearch = new PexelsImageSearch(); List<String> imageUrls = imageSearch.searchPicturesForChinese(query,30); // 将图片的URL转为ImageSearchResult对象 return convertToImageSearchResults(imageUrls); }

可以看到在searchImage方法中,使用传统new对象的方式进行生成PexelsImageSearch对象,这也是导致 apikey ,baiduAppId ,baiduApiKey为null的关键因素。

为什么呢 ?

问题分析

其实在 Spring 中,@Value 注解的工作原理依赖于 Spring 容器的依赖注入(Dependency Injection, DI)机制。当你使用 new 关键字创建对象时,Spring 容器并不参与该对象的创建过程,因此,@Value 注解所依赖的依赖注入也就无法生效。

说到这里,我们一起来回顾一下Spring依赖注入的工作原理:

Spring 通过其容器(通常是 ApplicationContext)来管理 Bean 的生命周期和依赖关系。当你使用 Spring 的注解(如 @Component、@Service 等)标记一个类时,Spring 容器会在启动时扫描这些类,并创建其 Bean 实例。

在 Bean 被实例化后,Spring 会处理该类中所有带有 @Autowired 或 @Value 注解的字段或方法,从而注入相应的依赖。如果类是通过 Spring 容器来管理的,@Value 注解会从配置文件中读取值,并注入到类的字段中。

但是,当你使用 new 关键字直接创建类的实例时,Spring 容器并不会参与该对象的创建过程,因此 @Value 注解就无法发挥作用。

为什么 @Value 无法生效?

@Value 注解的作用是让 Spring 在实例化类时,从配置文件或环境变量中注入值。如果类是由 Spring 容器管理的,Spring 会在实例化后,扫描该类并解析 @Value 注解,并将对应的值注入到字段中。

但是,当你通过 new 关键字直接创建一个对象时,这个对象的实例并没有通过 Spring 容器进行管理。因此,Spring 并不会扫描这个对象,@Value 注解也无法获得注入的值。

他们两个有什么关系吗

当你使用 new 关键字来创建对象时,Spring 不会对这个对象进行管理,它只是一个普通的 Java 对象,Spring 不会去解析这个对象中的 @Value 注解,也不会注入任何依赖。

Java
复制代码
// 这样创建对象不会通过 Spring 容器进行管理 PexelsImageSearch imageSearch = new PexelsImageSearch();

如果你希望通过 Spring 容器来管理对象并让 @Value 生效,你必须使用 Spring 的容器来创建该对象。Spring 会自动处理对象的创建和依赖注入。

Java
复制代码
// 通过 Spring 容器来管理对象 PexelsImageSearch imageSearch = applicationContext.getBean(PexelsImageSearch.class);

回归正题,我们此时这里应该怎么修改代码呢,其实很简单,只要你明白了问题出在哪里,以及我们上面的一些简单分析,你肯定知道如何修改这个问题。

首先需要在PexelsImageSearch类中添加 @Component 注解,交给Spring进行管理。

然后在ImageSearchApiFacade类中进行注入。

Java
复制代码
private final PexelsImageSearch pexelsImageSearch;

接着我们来修改一下searchImage方法,丢弃原来的new对象的方式。

Java
复制代码
public List<ImageSearchResult> searchImage(String query) { // 先进行中文转英文 List<String> imageUrls = pexelsImageSearch.searchPicturesForChinese(query, 18); // 将图片的URL转为ImageSearchResult对象 return convertToImageSearchResults(imageUrls); }

注意:别忘了修改以图搜图controller的调用方式哦。

完整代码:

PexelsImageSearch

Java
复制代码
package org.leocoder.picture.api.imagesearch.sub; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.qcloud.cos.utils.Md5Utils; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * @author : 程序员Leo * @version 1.0 * @date 2025-02-11 13:50 * @description : 使用 Pexels API 进行图片搜索,不指定页面和每页数量 */ @Slf4j @Component public class PexelsImageSearch { @Value("${pexels.apiKey}") private String apiKey; private static final String ENDPOINT = "https://api.pexels.com/v1/search"; @Value("${baidu.baiduAppId}") private String baiduAppId; @Value("${baidu.baiduApiKey}") private String baiduApiKey; private static final String BAIDU_TRANSLATE_ENDPOINT = "https://fanyi-api.baidu.com/api/trans/vip/translate"; private static final int DEFAULT_PAGE_SIZE = 12; /** * 使用百度翻译将中文转为英文 * * @param query 中文搜索关键词 * @return 英文翻译结果 */ public String translateChineseToEnglish(String query) { try { // 构建百度翻译请求参数 String salt = String.valueOf(System.currentTimeMillis()); String sign = baiduAppId + query + salt + baiduApiKey; String signMd5 = Md5Utils.md5Hex(sign); String url = BAIDU_TRANSLATE_ENDPOINT + "?q=" + query + "&from=zh&to=en&appid=" + baiduAppId + "&salt=" + salt + "&sign=" + signMd5; // 发起HTTP请求 HttpResponse response = HttpRequest.get(url).execute(); if (response.getStatus() == 200) { String responseBody = response.body(); // 使用 ObjectMapper 来解析 JSON 数据 ObjectMapper objectMapper = new ObjectMapper(); JsonNode resultNode = objectMapper.readTree(responseBody); if (resultNode != null && resultNode.has("trans_result")) { return resultNode.get("trans_result").get(0).get("dst").asText(); } } } catch (Exception e) { log.error("调用百度翻译 API 失败", e); } // 如果翻译失败,返回原始中文 return query; } /** * 使用 Pexels API 搜索图片 * * @param query 搜索关键词(英文) * @return 返回的图片URL列表 */ public List<String> searchPictures(String query, int totalCount) { List<String> imageUrls = new ArrayList<>(); int page = 1; int fetchedImages = 0; while (fetchedImages < totalCount) { String apiUrl = String.format( "%s?query=%s&per_page=%d&page=%d", ENDPOINT, query, DEFAULT_PAGE_SIZE, page ); try { OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(apiUrl) .addHeader("Authorization", apiKey) .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new IOException("Pexels API 调用失败,状态码:" + response.code()); } String responseBody = response.body().string(); ObjectMapper objectMapper = new ObjectMapper(); JsonNode photosNode = objectMapper.readTree(responseBody).get("photos"); if (photosNode != null) { for (JsonNode photoNode : photosNode) { // 获取每张图片的原图URL imageUrls.add(photoNode.get("src").get("original").asText()); fetchedImages++; // 如果已经抓取到足够的图片,跳出循环 if (fetchedImages >= totalCount) { break; } } } page++; // 增加页码 // 如果结果为空或已达到所需数量,停止请求 if (photosNode == null || fetchedImages >= totalCount) { break; } } catch (IOException e) { log.error("调用 Pexels API 失败", e); break; } } return imageUrls; } /** * 主方法:搜索中文关键词,翻译为英文并搜索图片 */ public List<String> searchPicturesForChinese(String query, int totalCount) { String translatedQuery = translateChineseToEnglish(query); return searchPictures(translatedQuery, totalCount); } public static void main(String[] args) { // 测试:使用中文搜索并进行翻译,获取 30 张图片 PexelsImageSearch imageSearch = new PexelsImageSearch(); List<String> imageUrls = imageSearch.searchPicturesForChinese("狗", 18); System.out.println("搜索到的图片URLs: "); imageUrls.forEach(System.out::println); } }

ImageSearchApiFacade

Java
复制代码
package org.leocoder.picture.api.imagesearch; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.leocoder.picture.api.imagesearch.model.ImageSearchResult; import org.leocoder.picture.api.imagesearch.sub.PexelsImageSearch; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author : 程序员Leo * @version 1.0 * @date 2025-02-11 13:50 * @description : 使用 Pexels API 进行图片搜索,不指定页面和每页数量 */ @Slf4j @Component @RequiredArgsConstructor public class ImageSearchApiFacade { private final PexelsImageSearch pexelsImageSearch; /** * 搜索图片 * * @param query 图片关键字(中文) * @return 图片搜索结果列表 */ public List<ImageSearchResult> searchImage(String query) { // 先进行中文转英文 List<String> imageUrls = pexelsImageSearch.searchPicturesForChinese(query, 18); // 将图片的URL转为ImageSearchResult对象 return convertToImageSearchResults(imageUrls); } /** * 将图片URL列表转换为ImageSearchResult列表 * * @param imageUrls 图片URL列表 * @return ImageSearchResult列表 */ private List<ImageSearchResult> convertToImageSearchResults(List<String> imageUrls) { List<ImageSearchResult> imageSearchResults = new ArrayList<>(); for (String url : imageUrls) { ImageSearchResult result = new ImageSearchResult(); result.setThumbUrl(url); result.setFromUrl(url); imageSearchResults.add(result); } return imageSearchResults; } }

PictureController

Java
复制代码
@ApiOperation(value = "以图搜图") @PostMapping("/search/picture") public Result<List<ImageSearchResult>> searchPictureByPicture(@RequestBody SearchPictureByPictureRequest requestParam) { ThrowUtils.throwIf(ObjectUtil.isNull(requestParam), ErrorCode.PARAMS_ERROR); Long pictureId = requestParam.getPictureId(); ThrowUtils.throwIf(ObjectUtil.isNull(pictureId) || pictureId <= 0, ErrorCode.PARAMS_ERROR); Picture oldPicture = pictureService.getById(pictureId); ThrowUtils.throwIf(ObjectUtil.isNull(oldPicture), ErrorCode.NOT_FOUND_ERROR); // 使用Pexels API进行图片搜索 List<ImageSearchResult> resultList = imageSearchApiFacade.searchImage(oldPicture.getName()); return ResultUtils.success(resultList); }

重启测试

此时我们再次启动项目进行测试。

Google Chrome 2025-02-20 19.31.22

发现一切都正常了哈,大功告成!

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
无名小卒
下载 APP