《Flutter 接入 Wallhaven API 惊魂记:从技术狂喜到 “社死” 边缘的 24 小时!》

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.networkImage.asset 等)。这些加载方式是 Flutter 框架提供的内置功能,它们直接通过底层的网络请求系统(如 HttpClient)发起图片请求,而不是通过 Dio 库。因此,即使 Dio 配置了代理,这些请求也不会受 Dio 配置影响。
  • 通过其他图片加载库加载图片 (如 cached_network_image 等)。这些库可能有自己的网络请求实现方式,不一定使用 Dio。因此,这些请求也不会走 Dio 的代理配置。

该方案只能使接口正常获取到数据,拿到图片路径后依然无法正常加载图片。ai提供的解决方案是通过 dio 下载图片后在展示图片,因为嫌麻烦,没有尝试过行不行,大家有兴趣可以自行测试。

2.2 方案二:配置全局 HttpOverrides.global 代理

  1. 首先判断本地的 7890 端口是否开启代理,基本所有的VPN软件都是走的这个端口。
  2. 如果开启了代理,全局的网络都走 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官网: 自己的软件:限制级别选 模糊 即可,和官网一模一样。

4、项目开源地址

开源地址

gitee.com/zsnoin-can/...

软件体验地址

gitee.com/zsnoin-can/...

详细介绍

juejin.cn/post/749455...

相关推荐
耳東陈2 小时前
Flutter开箱即用一站式解决方案2.0-支持任意Widget的跑马灯
flutter
耳東陈2 小时前
Flutter开箱即用一站式解决方案2.0-智能刷新列表
flutter
kaikaile19956 小时前
windows配置supervisor实现nginx自启
运维·windows·nginx
张风捷特烈6 小时前
每日一题 Flutter#12 | StatefulWidget 从诞生到状态类build 的流程
android·flutter·面试
吴敬悦1 天前
在 Flutter 中集成 C/C++ 代码 BLE LC3( 基于 AI 教程 )
flutter·ai编程
衿璃1 天前
Flutter Navigator 锁定错误
flutter
黄豆匿zlib1 天前
Python中的其他数据结构:除了列表和元组,还有哪些?
数据结构·windows·python
90wunch1 天前
更进一步深入的研究ObRegisterCallBack
c++·windows·安全
啾啾Fun1 天前
Python类型处理与推导式
开发语言·windows·python