v1.0.1
This commit is contained in:
81
lib/requests/common/api.dart
Normal file
81
lib/requests/common/api.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:crypton/crypton.dart';
|
||||
import 'package:flutter_dmzj/app/app_error.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
|
||||
class Api {
|
||||
static const String DMZJ_DOMAIN_NAME = "dmzj.com";
|
||||
static const String IDMZJ_DOMAIN_NAME = "idmzj.com";
|
||||
static const String MUWAI_DOMAIN_NAME = "muwai.com";
|
||||
static const String DOMAIN_NAME = "zaimanhua.com";
|
||||
|
||||
/// V3接口,无加密
|
||||
static const String BASE_URL = "https://v4api.zaimanhua.com/app/v1";
|
||||
|
||||
/// 用户
|
||||
static const String BASE_URL_USER = "https://account-api.zaimanhua.com/v1";
|
||||
|
||||
/// Interface
|
||||
static const String BASE_URL_INTERFACE =
|
||||
"http://nninterface.$IDMZJ_DOMAIN_NAME";
|
||||
|
||||
/// V4 API的密钥
|
||||
static const V4_PRIVATE_KEY =
|
||||
"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAK8nNR1lTnIfIes6oRWJNj3mB6OssDGx0uGMpgpbVCpf6+VwnuI2stmhZNoQcM417Iz7WqlPzbUmu9R4dEKmLGEEqOhOdVaeh9Xk2IPPjqIu5TbkLZRxkY3dJM1htbz57d/roesJLkZXqssfG5EJauNc+RcABTfLb4IiFjSMlTsnAgMBAAECgYEAiz/pi2hKOJKlvcTL4jpHJGjn8+lL3wZX+LeAHkXDoTjHa47g0knYYQteCbv+YwMeAGupBWiLy5RyyhXFoGNKbbnvftMYK56hH+iqxjtDLnjSDKWnhcB7089sNKaEM9Ilil6uxWMrMMBH9v2PLdYsqMBHqPutKu/SigeGPeiB7VECQQDizVlNv67go99QAIv2n/ga4e0wLizVuaNBXE88AdOnaZ0LOTeniVEqvPtgUk63zbjl0P/pzQzyjitwe6HoCAIpAkEAxbOtnCm1uKEp5HsNaXEJTwE7WQf7PrLD4+BpGtNKkgja6f6F4ld4QZ2TQ6qvsCizSGJrjOpNdjVGJ7bgYMcczwJBALvJWPLmDi7ToFfGTB0EsNHZVKE66kZ/8Stx+ezueke4S556XplqOflQBjbnj2PigwBN/0afT+QZUOBOjWzoDJkCQClzo+oDQMvGVs9GEajS/32mJ3hiWQZrWvEzgzYRqSf3XVcEe7PaXSd8z3y3lACeeACsShqQoc8wGlaHXIJOHTcCQQCZw5127ZGs8ZDTSrogrH73Kw/HvX55wGAeirKYcv28eauveCG7iyFR0PFB/P/EDZnyb+ifvyEFlucPUI0+Y87F";
|
||||
static Uint8List decryptV4(String text) {
|
||||
try {
|
||||
RSAKeypair rsaKeypair =
|
||||
RSAKeypair(RSAPrivateKey.fromString(V4_PRIVATE_KEY));
|
||||
var decrypted = rsaKeypair.privateKey.decryptData(base64.decode(text));
|
||||
return decrypted;
|
||||
} catch (e) {
|
||||
throw AppError('返回数据解密失败');
|
||||
}
|
||||
}
|
||||
|
||||
/// 签名
|
||||
static String sign(String content, String mode) {
|
||||
var utf8Content = utf8.encode(mode + content);
|
||||
|
||||
return md5.convert(utf8Content).toString();
|
||||
}
|
||||
|
||||
static const String VERSION = "3.8.2";
|
||||
static String get timeStamp =>
|
||||
(DateTime.now().millisecondsSinceEpoch / 1000).toStringAsFixed(0);
|
||||
|
||||
/// 默认的参数
|
||||
static Map<String, dynamic> getDefaultParameter({bool withUid = false}) {
|
||||
var map = <String, dynamic>{
|
||||
"channel": "android",
|
||||
//"version": VERSION,
|
||||
"timestamp": timeStamp
|
||||
};
|
||||
if (withUid && UserService.instance.logined.value) {
|
||||
map.addAll({"uid": UserService.instance.userId});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// 小说正文链接
|
||||
static String getNovelContentUrl(
|
||||
{required int volumeId, required int chapterId}) {
|
||||
// var path = "/lnovel/${volumeId}_$chapterId.txt";
|
||||
// var ts = (DateTime.now().millisecondsSinceEpoch / 1000).toStringAsFixed(0);
|
||||
// var key =
|
||||
// "IBAAKCAQEAsUAdKtXNt8cdrcTXLsaFKj9bSK1nEOAROGn2KJXlEVekcPssKUxSN8dsfba51kmHM";
|
||||
// key += path;
|
||||
// key += ts;
|
||||
// key = md5.convert(utf8.encode(key)).toString().toLowerCase();
|
||||
|
||||
// return "http://jurisdiction.idmzj.com$path?t=$ts&k=$key";
|
||||
|
||||
//https://v4api.zaimanhua.com/app/v1/novel/download/chapter?volumeId=12458&chapterId=127221
|
||||
return "$BASE_URL/novel/download/chapter?volumeId=$volumeId&chapterId=$chapterId";
|
||||
}
|
||||
}
|
||||
53
lib/requests/common/custom_interceptor.dart
Normal file
53
lib/requests/common/custom_interceptor.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_dmzj/app/log.dart';
|
||||
|
||||
class CustomInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
options.extra["ts"] = DateTime.now().millisecondsSinceEpoch;
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
var time =
|
||||
DateTime.now().millisecondsSinceEpoch - err.requestOptions.extra["ts"];
|
||||
Log.e('''【HTTP请求错误】 耗时:${time}ms
|
||||
Request Method:${err.requestOptions.method}
|
||||
Response Code:${err.response?.statusCode}
|
||||
Request URL:${err.requestOptions.uri}
|
||||
Request Query:${err.requestOptions.queryParameters}
|
||||
Request Data:${err.requestOptions.data}
|
||||
Request Headers:${err.requestOptions.headers}
|
||||
Response Headers:${err.response?.headers.map}
|
||||
Response Data:${err.response?.data}''', err.stackTrace);
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
var time = DateTime.now().millisecondsSinceEpoch -
|
||||
response.requestOptions.extra["ts"];
|
||||
if (response.requestOptions.uri.toString().contains(".txt")) {
|
||||
Log.i(
|
||||
'''【HTTP请求响应】 耗时:${time}ms
|
||||
Request Method:${response.requestOptions.method}
|
||||
Request Code:${response.statusCode}
|
||||
Request URL:${response.requestOptions.uri}''',
|
||||
);
|
||||
return super.onResponse(response, handler);
|
||||
}
|
||||
Log.i(
|
||||
'''【HTTP请求响应】 耗时:${time}ms
|
||||
Request Method:${response.requestOptions.method}
|
||||
Request Code:${response.statusCode}
|
||||
Request URL:${response.requestOptions.uri}
|
||||
Request Query:${response.requestOptions.queryParameters}
|
||||
Request Data:${response.requestOptions.data}
|
||||
Request Headers:${response.requestOptions.headers}
|
||||
Response Headers:${response.headers.map}
|
||||
Response Data:${response.data}''',
|
||||
);
|
||||
super.onResponse(response, handler);
|
||||
}
|
||||
}
|
||||
260
lib/requests/common/http_client.dart
Normal file
260
lib/requests/common/http_client.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_dmzj/app/app_error.dart';
|
||||
import 'package:flutter_dmzj/requests/common/api.dart';
|
||||
import 'package:flutter_dmzj/requests/common/custom_interceptor.dart';
|
||||
import 'package:flutter_dmzj/services/user_service.dart';
|
||||
|
||||
class HttpClient {
|
||||
static HttpClient? _httpUtil;
|
||||
|
||||
static HttpClient get instance {
|
||||
_httpUtil ??= HttpClient();
|
||||
return _httpUtil!;
|
||||
}
|
||||
|
||||
late Dio dio;
|
||||
HttpClient() {
|
||||
dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: const Duration(seconds: 20),
|
||||
receiveTimeout: const Duration(seconds: 20),
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
),
|
||||
);
|
||||
dio.interceptors.add(CustomInterceptor());
|
||||
}
|
||||
|
||||
/// Get请求
|
||||
/// * [path] 请求链接
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
/// * [needLogin] 是否需要登录
|
||||
/// * [withDefaultParameter] 是否需要带上一些默认参数
|
||||
/// * [responseType] 返回的类型
|
||||
Future<dynamic> get(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool withDefaultParameter = true,
|
||||
bool needLogin = false,
|
||||
ResponseType responseType = ResponseType.json,
|
||||
bool checkCode = false,
|
||||
}) async {
|
||||
Map<String, dynamic> header = {};
|
||||
queryParameters ??= <String, dynamic>{};
|
||||
var query = Api.getDefaultParameter(withUid: needLogin);
|
||||
if (withDefaultParameter) {
|
||||
queryParameters.addAll(query);
|
||||
}
|
||||
if (needLogin) {
|
||||
if (UserService.instance.logined.value) {
|
||||
header['Authorization'] = 'Bearer ${UserService.instance.dmzjToken}';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var result = await dio.get(
|
||||
baseUrl + path,
|
||||
queryParameters: queryParameters,
|
||||
options: Options(
|
||||
responseType: responseType,
|
||||
headers: header,
|
||||
),
|
||||
cancelToken: cancel,
|
||||
);
|
||||
if (checkCode && result.data is Map) {
|
||||
var data = result.data as Map;
|
||||
if (data['errno'] == 0) {
|
||||
return result.data['data'];
|
||||
} else {
|
||||
throw AppError(
|
||||
result.data['errmsg'].toString(),
|
||||
code: int.tryParse(result.data['errno'].toString()) ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result.data;
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.cancel) {
|
||||
rethrow;
|
||||
}
|
||||
if (e.type == DioExceptionType.badResponse) {
|
||||
return throw AppError("请求失败:${e.response?.statusCode ?? -1}");
|
||||
}
|
||||
throw AppError("请求失败,请检查网络");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get 请求,返回JSON
|
||||
/// * [path] 请求链接
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
/// * [needLogin] 是否需要登录
|
||||
/// * [withDefaultParameter] 是否需要带上一些默认参数
|
||||
Future<dynamic> getJson(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool withDefaultParameter = true,
|
||||
bool needLogin = false,
|
||||
bool checkCode = false,
|
||||
}) async {
|
||||
var result = await get(
|
||||
path,
|
||||
queryParameters: queryParameters,
|
||||
baseUrl: baseUrl,
|
||||
cancel: cancel,
|
||||
withDefaultParameter: withDefaultParameter,
|
||||
needLogin: needLogin,
|
||||
responseType: ResponseType.json,
|
||||
checkCode: checkCode,
|
||||
);
|
||||
if (result is Map || result is List) {
|
||||
return result;
|
||||
} else if (result is String) {
|
||||
return jsonDecode(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Get 请求,返回Text
|
||||
/// * [path] 请求链接
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
/// * [needLogin] 是否需要登录
|
||||
/// * [withDefaultParameter] 是否需要带上一些默认参数
|
||||
Future<dynamic> getText(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool withDefaultParameter = true,
|
||||
bool needLogin = false,
|
||||
}) async {
|
||||
return await get(
|
||||
path,
|
||||
queryParameters: queryParameters,
|
||||
baseUrl: baseUrl,
|
||||
cancel: cancel,
|
||||
withDefaultParameter: withDefaultParameter,
|
||||
needLogin: needLogin,
|
||||
responseType: ResponseType.plain,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get 请求,返回解密后Bytes
|
||||
/// * [path] 请求链接
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
/// * [needLogin] 是否需要登录
|
||||
/// * [withDefaultParameter] 是否需要带上一些默认参数
|
||||
Future<Uint8List> getEncryptV4(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool withDefaultParameter = true,
|
||||
bool needLogin = false,
|
||||
}) async {
|
||||
var result = await get(
|
||||
path,
|
||||
queryParameters: queryParameters,
|
||||
baseUrl: baseUrl,
|
||||
cancel: cancel,
|
||||
withDefaultParameter: withDefaultParameter,
|
||||
needLogin: needLogin,
|
||||
responseType: ResponseType.plain,
|
||||
);
|
||||
var resultBytes = Api.decryptV4(result);
|
||||
return resultBytes;
|
||||
}
|
||||
|
||||
/// Get 请求,返回byte
|
||||
/// * [path] 请求链接
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
/// * [needLogin] 是否需要登录
|
||||
/// * [withDefaultParameter] 是否需要带上一些默认参数
|
||||
Future<dynamic> getBytes(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool withDefaultParameter = true,
|
||||
bool needLogin = false,
|
||||
}) async {
|
||||
return await get(
|
||||
path,
|
||||
queryParameters: queryParameters,
|
||||
baseUrl: baseUrl,
|
||||
cancel: cancel,
|
||||
withDefaultParameter: withDefaultParameter,
|
||||
needLogin: needLogin,
|
||||
responseType: ResponseType.bytes,
|
||||
);
|
||||
}
|
||||
|
||||
/// Post请求,返回Map
|
||||
/// * [path] 请求链接
|
||||
/// * [data] 发送数据
|
||||
/// * [queryParameters] 请求参数
|
||||
/// * [cancel] 任务取消Token
|
||||
Future<dynamic> postJson(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Map<String, dynamic>? data,
|
||||
String baseUrl = Api.BASE_URL,
|
||||
CancelToken? cancel,
|
||||
bool formUrlEncoded = false,
|
||||
bool checkCode = false,
|
||||
bool needLogin = false,
|
||||
}) async {
|
||||
Map<String, dynamic> header = {};
|
||||
queryParameters ??= {};
|
||||
if (needLogin) {
|
||||
if (UserService.instance.logined.value) {
|
||||
header['Authorization'] = 'Bearer ${UserService.instance.dmzjToken}';
|
||||
}
|
||||
}
|
||||
try {
|
||||
var result = await dio.post(
|
||||
baseUrl + path,
|
||||
queryParameters: queryParameters,
|
||||
data: data,
|
||||
options: Options(
|
||||
responseType: ResponseType.json,
|
||||
headers: header,
|
||||
contentType:
|
||||
formUrlEncoded ? Headers.formUrlEncodedContentType : null,
|
||||
),
|
||||
cancelToken: cancel,
|
||||
);
|
||||
var jsonMap = result.data;
|
||||
if (jsonMap is String) {
|
||||
jsonMap = jsonDecode(jsonMap);
|
||||
}
|
||||
if (checkCode) {
|
||||
var data = result.data as Map;
|
||||
if (data['errno'] == 0) {
|
||||
return result.data['data'];
|
||||
} else {
|
||||
throw AppError(
|
||||
result.data['errmsg'].toString(),
|
||||
code: int.tryParse(result.data['errno'].toString()) ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result.data;
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.badResponse) {
|
||||
return throw AppError("请求失败:状态码:${e.response?.statusCode ?? -1}");
|
||||
}
|
||||
throw AppError("请求失败,请检查网络");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user