diff --git a/lib/http/api.dart b/lib/http/api.dart new file mode 100644 index 00000000..99abdbf4 --- /dev/null +++ b/lib/http/api.dart @@ -0,0 +1,7 @@ +class Api { + // 推荐视频 + static const String recommendList = '/x/web-interface/index/top/feed/rcmd'; + // 视频详情 + // 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921 + static const String videoDetail = '/x/web-interface/view'; +} diff --git a/lib/http/constants.dart b/lib/http/constants.dart new file mode 100644 index 00000000..619060a4 --- /dev/null +++ b/lib/http/constants.dart @@ -0,0 +1,4 @@ +class HttpString { + static const String baseUrl = 'https://www.bilibili.com'; + static const String baseApiUrl = 'https://api.bilibili.com'; +} diff --git a/lib/http/init.dart b/lib/http/init.dart new file mode 100644 index 00000000..0ac94f85 --- /dev/null +++ b/lib/http/init.dart @@ -0,0 +1,181 @@ +// ignore_for_file: avoid_print +import 'dart:developer'; +import 'dart:io'; +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'package:cookie_jar/cookie_jar.dart'; +import 'package:pilipala/utils/utils.dart'; +import 'package:pilipala/http/constants.dart'; +import 'package:pilipala/http/interceptor.dart'; +import 'package:dio_http2_adapter/dio_http2_adapter.dart'; +import 'package:dio_cookie_manager/dio_cookie_manager.dart'; + +class Request { + static final Request _instance = Request._internal(); + + factory Request() => _instance; + + static Dio dio = Dio() + ..httpClientAdapter = Http2Adapter( + ConnectionManager( + idleTimeout: const Duration(milliseconds: 10000), + // Ignore bad certificate + onClientCreate: (_, config) => config.onBadCertificate = (_) => true, + ), + ); + + /// 设置cookie + static setCookie() async { + var cookiePath = await Utils.getCookiePath(); + var cookieJar = PersistCookieJar( + ignoreExpires: true, + storage: FileStorage(cookiePath), + ); + + dio.interceptors.add(CookieManager(cookieJar)); + + var cookie = await CookieManager(cookieJar) + .cookieJar + .loadForRequest(Uri.parse(HttpString.baseUrl)); + if (cookie.isEmpty) { + try { + await Request().get(HttpString.baseUrl); + } catch (e) { + log("setCookie, ${e.toString()}"); + } + } + } + + /* + * config it and create + */ + Request._internal() { + //BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 + BaseOptions options = BaseOptions( + //请求基地址,可以包含子路径 + baseUrl: HttpString.baseApiUrl, + //连接服务器超时时间,单位是毫秒. + connectTimeout: const Duration(milliseconds: 12000), + //响应流上前后两次接受到数据的间隔,单位为毫秒。 + receiveTimeout: const Duration(milliseconds: 12000), + //Http请求头. + // headers: { + // 'cookie': '', + // }, + ); + + dio.options = options; + + //添加拦截器 + dio.interceptors + ..add(ApiInterceptor()) + // 日志拦截器 输出请求、响应内容 + ..add(LogInterceptor( + request: false, + requestHeader: false, + responseHeader: false, + )); + dio.transformer = BackgroundTransformer(); + dio.options.validateStatus = (status) { + return status! >= 200 && status < 300 || status == 304 || status == 302; + }; + } + + /* + * get请求 + */ + get(url, {data, cacheOptions, options, cancelToken, extra}) async { + Response response; + Options options; + String ua = 'pc'; + ResponseType resType = ResponseType.json; + if (extra != null) { + ua = extra!['ua'] ?? 'pc'; + resType = extra!['resType'] ?? ResponseType.json; + } + if (cacheOptions != null) { + cacheOptions.headers = {'user-agent': headerUa(ua)}; + options = cacheOptions; + } else { + options = Options(); + options.headers = {'user-agent': headerUa(ua)}; + options.responseType = resType; + } + try { + response = await dio.get( + url, + queryParameters: data, + options: options, + cancelToken: cancelToken, + ); + return response; + } on DioError catch (e) { + print('get error: $e'); + return Future.error(ApiInterceptor.dioError(e)); + } + } + + /* + * post请求 + */ + post(url, {data, options, cancelToken, extra}) async { + print('post-data: $data'); + Response response; + try { + response = await dio.post( + url, + data: data, + options: options, + cancelToken: cancelToken, + ); + print('post success: ${response.data}'); + return response; + } on DioError catch (e) { + print('post error: $e'); + return Future.error(ApiInterceptor.dioError(e)); + } + } + + /* + * 下载文件 + */ + downloadFile(urlPath, savePath) async { + Response response; + try { + response = await dio.download(urlPath, savePath, + onReceiveProgress: (int count, int total) { + //进度 + // print("$count $total"); + }); + print('downloadFile success: ${response.data}'); + + return response.data; + } on DioError catch (e) { + print('downloadFile error: $e'); + return Future.error(ApiInterceptor.dioError(e)); + } + } + + /* + * 取消请求 + * + * 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。 + * 所以参数可选 + */ + void cancelRequests(CancelToken token) { + token.cancel("cancelled"); + } + + String headerUa(ua) { + String headerUa = ''; + if (ua == 'mob') { + headerUa = Platform.isIOS + ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1' + : 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36'; + } else { + headerUa = + 'Mozilla/5.0 (MaciMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'; + } + return headerUa; + } +} diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart new file mode 100644 index 00000000..d7140b00 --- /dev/null +++ b/lib/http/interceptor.dart @@ -0,0 +1,71 @@ +import 'package:dio/dio.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; + +class ApiInterceptor extends Interceptor { + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + // print("请求之前"); + // 在请求之前添加头部或认证信息 + // options.headers['Authorization'] = 'Bearer token'; + // options.headers['Content-Type'] = 'application/json'; + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + // print("响应之前"); + handler.next(response); + } + + @override + void onError(DioError err, ErrorInterceptorHandler handler) { + // 处理网络请求错误 + + handler.next(err); + super.onError(err, handler); + } + + static Future dioError(DioError error) async { + switch (error.type) { + case DioErrorType.badCertificate: + return '证书有误!'; + case DioErrorType.badResponse: + return '服务器异常,请稍后重试!'; + case DioErrorType.cancel: + return "请求已被取消,请重新请求"; + case DioErrorType.connectionError: + return '连接错误,请检查网络设置'; + case DioErrorType.connectionTimeout: + return "网络连接超时,请检查网络设置"; + case DioErrorType.receiveTimeout: + return "响应超时,请稍后重试!"; + case DioErrorType.sendTimeout: + return "发送请求超时,请检查网络设置"; + case DioErrorType.unknown: + var res = await checkConect(); + return "$res 网络异常,请稍后重试!"; + default: + return "Dio异常"; + } + } + + static Future checkConect() async { + final connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.mobile) { + return 'connected with mobile network'; + } else if (connectivityResult == ConnectivityResult.wifi) { + return 'connected with wifi network'; + } else if (connectivityResult == ConnectivityResult.ethernet) { + // I am connected to a ethernet network. + } else if (connectivityResult == ConnectivityResult.vpn) { + // I am connected to a vpn network. + // Note for iOS and macOS: + // There is no separate network interface type for [vpn]. + // It returns [other] on any device (also simulator) + } else if (connectivityResult == ConnectivityResult.other) { + // I am connected to a network which is not in the above mentioned networks. + } else if (connectivityResult == ConnectivityResult.none) { + return 'not connected to any network'; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 094cdc25..b285baf6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,13 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:dynamic_color/dynamic_color.dart'; +import 'package:pilipala/http/init.dart'; import 'package:pilipala/router/app_pages.dart'; import 'package:pilipala/pages/main/view.dart'; -void main() { +void main() async { WidgetsFlutterBinding.ensureInitialized(); + await Request.setCookie(); runApp(const MyApp()); } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 00000000..299fb77f --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,16 @@ +// 工具函数 +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; + +class Utils { + static Future getCookiePath() async { + Directory tempDir = await getApplicationSupportDirectory(); + String tempPath = "${tempDir.path}/.plpl/"; + Directory dir = Directory(tempPath); + bool b = await dir.exists(); + if (!b) { + dir.createSync(recursive: true); + } + return tempPath; + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 437a5013..65feaf20 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import connectivity_plus import dynamic_color import path_provider_foundation import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 567e40fb..cc2b687d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" async: dependency: transitive description: @@ -65,6 +73,30 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.17.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: d73575bb66216738db892f72ba67dc478bd3b5490fbbcf43644b57645eabc822 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.4" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.4" + cookie_jar: + dependency: "direct main" + description: + name: cookie_jar + sha256: d1cc6516a190ba667941f722b6365d202caff3dacb38de24268b8d6ff1ec8a1d + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" crypto: dependency: transitive description: @@ -81,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.8" dio: dependency: "direct main" description: @@ -89,6 +129,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" + dio_cookie_manager: + dependency: "direct main" + description: + name: dio_cookie_manager + sha256: b45f11c2fcbccf39c5952ab68910b3a155486c4fa730ceb4ce867c4943169ea1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + dio_http2_adapter: + dependency: "direct main" + description: + name: dio_http2_adapter + sha256: b06a02faaff972c4809c4ada7a2f71f6c74ce21f0feee79b357f2a9590c049d4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" dynamic_color: dependency: "direct main" description: @@ -155,6 +211,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get: dependency: "direct main" description: @@ -171,6 +232,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.13.5" + http2: + dependency: transitive + description: + name: http2 + sha256: "58805ebc6513eed3b98ee0a455a8357e61d187bf2e0fdc1e53120770f78de258" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" http_parser: dependency: transitive description: @@ -219,6 +288,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.8.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.0" octo_image: dependency: transitive description: @@ -291,6 +368,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.11.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.0" platform: dependency: transitive description: @@ -440,6 +525,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" + xml: + dependency: transitive + description: + name: xml + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.2.2" sdks: dart: ">=2.19.6 <3.0.0" flutter: ">=3.4.0-17.0.pre" diff --git a/pubspec.yaml b/pubspec.yaml index 9cdd2569..bb429eb2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,10 @@ dependencies: # 网络 dio: ^5.1.1 + cookie_jar: ^3.0.0 + dio_http2_adapter: ^2.2.0 + dio_cookie_manager: ^2.1.4 + connectivity_plus: ^3.0.4 # 图片 cached_network_image: ^3.2.3 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index e4899a64..ae0cf3ff 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 841e8c4a..e47678f2 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus dynamic_color )