Flutter 接入 Wallhaven API 惊魂记:从技术狂喜到 "社死" 边缘的 24 小时
闷热的办公室里,我专注地在电脑上用 Flutter 开发桌面壁纸软件。当发现 Wallhaven 丰富的 API 接口时,那些 "categories""purity" 等筛选参数,仿佛预示着能轻松实现高质量图源整合,我立刻兴奋地编写调用代码。
可现实很快泼来冷水。Wallhaven 官网的 API 文档对参数描述模糊不清,"categories" 默认值与组合规则、"purity" 的内容过滤标准都语焉不详。我只能坐在工位反复调试参数,刷新接口。
起初一切正常,直到调整 "categories" 和 "purity" 组合参数时,一张限制级图片突然在电脑屏幕上弹出。我慌得手忙脚乱,鼠标乱点,余光瞥见隔壁同事投来疑惑的目光,后背瞬间冷汗直冒。好不容易关掉页面,尴尬得如坐针毡。
冷静后排查发现,"purity" 默认不排除限制级内容,"categories" 默认值也会返回边缘内容。我迅速在参数中加入 "SFW" 标识,精准设置类别,并增加本地图片审核逻辑。经过数小时调试,终于让软件呈现出符合定位的壁纸。
这次经历给我敲响警钟:API 接入不能轻信文档,内容筛选参数务必反复测试,在公共场合调试更要提前评估风险。技术路上,一个小疏忽就可能带来 "社死" 危机,严谨才是开发的生存法则。
1、Wallhaven 简介
Wallhaven 堪称壁纸爱好者的天堂,是全球知名的高质量壁纸下载网站。它拥有超百万张高品质壁纸,内容涵盖极为丰富,无论是二次元动漫迷钟情的精美插画,摄影爱好者喜爱的壮丽风景,还是游戏玩家热衷的游戏场景,甚至是人物特写等各类风格壁纸,都能在这里轻松找到。
在搜索和筛选功能方面,Wallhaven 表现得十分出色。用户既可以通过输入英文关键词精准搜索目标壁纸,也能查看热门标签,快速浏览当下流行的壁纸分类。网站还设有排行榜,依据浏览量、点赞数等数据,为用户呈现最受欢迎的壁纸作品。值得一提的是,其独特的 "categories" 和 "purity" 等筛选参数,本应助力用户进一步精准定位所需壁纸,却因官网文档描述模糊,给开发者带来不少困扰,这也是我此次接入 API 时遭遇 "社死危机" 的重要原因。
官网地址
官网地址: wallhaven.cc/ ,wallhaven 国内是无法正常访问的,需要 VPN 才能访问。
接口文档
接口文档地址: wallhaven.cc/help/api ,这里只对 搜索api进行介绍,其它接口大家感兴趣可以自行前往查看。
基础路径:wallhaven.cc/api/v1/sear... , 参数前往官网查看吧,还是有点多的。
关键参数
- apikey:需要注册账号后生成,注册账号有的 ip 地址可能会失败,无法注册,多换几个ip试试,我是用日本IP才注册成功的。注册成功后通过下方操作即可生成自己的apikey参数。
- purity :100/110/111(sfw/sketchy/nsfw),
这个参数就是控制图片最大等级的,分别对应:安全、r16、r18!!!!!!!!!!
- sorting (整体排序): date_added , relevance, random, views, favorites, toplist,不知道relevance 有啥用,我把 relevance 换成 hot 参数,请求结果就和官网热门结果一致了,但是官网文档却没给出 hot 参数。
示例
/api/wallhaven.dart
dart
// wallhaven 壁纸api
// ignore_for_file: equal_keys_in_map
import 'package:dio/dio.dart';
import 'package:wallpaper_app/http/dio_instance.dart';
// 搜索壁纸
Future<Response> wallhavenSearch({
required String q,
required int page,
required String type,
String? ratio,
String? order,
String? apikey,
String? categories,
String? purity,
}) {
final params = {
'apikey': apikey,
'q': q,
'categories': categories,
'purity': purity,
'sorting': type,
'order': order,
'page': page.toString(),
'ratios': ratio,
};
return DioInstance.instance().get(
path: 'https://wallhaven.cc/api/v1/search',
param: params,
);
}
2、代理设置
Wallhaven 被墙了,接口自然也被墙,默认 flutter windows不走代理,就算你开着VPN也不能正常请求接口,需要对接口也做代理。
下面是一些踩坑记录和最终解决方法:
2.1 方案一:给dio网络做单独代理(初始方案)
以下是ai帮忙做的代理设置,因为不是最终方案,没做具体的记录,而且我的dio是经过封装的配置略有差异,但是都是给dio请求做的代理配置。
dio全局静态代理配置
通过 Dio 的HttpClientAdapter
设置固定代理,适合开发环境使用:
dart
import 'package:dio/dio.dart';
void main() {
Dio dio = Dio();
// 配置HTTP代理
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) {
return "PROXY 192.168.1.100:8888"; // 替换为你的代理服务器地址和端口
};
// 忽略证书验证(仅用于测试环境)
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
// 发起请求
dio.get('https://api.example.com/data');
}
方案弊端
- 通过 Flutter 内置的
Image
组件加载图片 (如Image.network
、Image.asset
等)。这些加载方式是 Flutter 框架提供的内置功能,它们直接通过底层的网络请求系统(如HttpClient
)发起图片请求,而不是通过 Dio 库。因此,即使 Dio 配置了代理,这些请求也不会受 Dio 配置影响。 - 通过其他图片加载库加载图片 (如
cached_network_image
等)。这些库可能有自己的网络请求实现方式,不一定使用 Dio。因此,这些请求也不会走 Dio 的代理配置。
该方案只能使接口正常获取到数据,拿到图片路径后依然无法正常加载图片。ai提供的解决方案是通过 dio 下载图片后在展示图片,因为嫌麻烦,没有尝试过行不行,大家有兴趣可以自行测试。
2.2 方案二:配置全局 HttpOverrides.global 代理
- 首先判断本地的 7890 端口是否开启代理,基本所有的VPN软件都是走的这个端口。
- 如果开启了代理,全局的网络都走 120.0.0.1:7890 代理。
HttpOverrides 全局代理
dart
······
······
// 判断代理是否开启
Future<bool> isProxyAvailable(String host, int port) async {
try {
final socket = await Socket.connect(host, port,
timeout: const Duration(milliseconds: 500));
socket.destroy();
return true;
} catch (_) {
return false;
}
}
void main() async {
······
const proxyHost = '127.0.0.1';
const proxyPort = 7890; // 根据你的代理端口修改
if (await isProxyAvailable(proxyHost, proxyPort)) {
HttpOverrides.global = MyHttpOverrides(proxyHost, proxyPort);
}
······
runApp(MultiProvider(
providers: [
······
],
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
······
······
}
class MyHttpOverrides extends HttpOverrides {
final String host;
final int port;
MyHttpOverrides(this.host, this.port);
@override
HttpClient createHttpClient(SecurityContext? context) {
var client = super.createHttpClient(context);
client.findProxy = (uri) => "PROXY $host:$port";
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
}
}
方案弊端
- 所有的网络都走了代理,对于这个项目完全多此一举。
- 代理状态(关闭/开启)变化时,必须重新启动软件接口才能正常访问,无法动态判读。
2.3 方案三:动态判断是否需要代理(方案二升级)
方案二已经解决dio和图片问题,稍作优化即可完美解决代理问题。
HttpOverrides 全局智能代理
- 不再需要判断代理是否开启,至少在国内 Wallhaven 现在必须开VPN才能正常访问。
- 直接根据请求中是否包含 wallhaven.cc 判断是否需要代理。
- 这样做只有包含 wallhaven.cc 的请求才走代理,其它接口均不走,就算代理状态变化了也不影响其它接口正常调用。
先定义一个代理管理文件 /lib/proxy/my_http_overrides.dart
dart
import 'dart:io';
class MyHttpOverrides extends HttpOverrides {
final String host = '127.0.0.1';
final int port = 7890;
@override
HttpClient createHttpClient(SecurityContext? context) {
var client = super.createHttpClient(context);
client.findProxy = (uri) {
// 这里动态判断是否需要代理
if (shouldUseProxy(uri.host)) {
return "PROXY $host:$port";
}
return "DIRECT";
};
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
}
}
// 你可以自定义判断逻辑,比如只对 wallhaven.cc 代理
bool shouldUseProxy(String host) {
// 只对 wallhaven.cc 及其子域名走代理
return host.contains('wallhaven.cc');
}
然后在main.dart中直接调用即可。
dart
void main() async {
······
HttpOverrides.global = MyHttpOverrides();
······
}
方案弊端
暂时没有发型,硬要说有的话可能就是 如果存在多个接口都需要代理时,你都要加上判断,优化一下判断逻辑即可。
3、效果预览
wallhaven官网: 自己的软件:限制级别选 模糊 即可,和官网一模一样。