This commit is contained in:
2026-03-07 17:24:59 +08:00
parent 4418ebecac
commit b0ec8ab4bd
417 changed files with 42546 additions and 2 deletions

View File

@@ -0,0 +1,462 @@
import 'dart:convert';
import 'package:flutter_dmzj/app/app_error.dart';
import 'package:flutter_dmzj/app/log.dart';
import 'package:flutter_dmzj/models/comic/author_model.dart';
import 'package:flutter_dmzj/models/comic/category_comic_model.dart';
import 'package:flutter_dmzj/models/comic/category_filter_model.dart';
import 'package:flutter_dmzj/models/comic/category_item_model.dart';
import 'package:flutter_dmzj/models/comic/chapter_detail_model.dart';
import 'package:flutter_dmzj/models/comic/chapter_detail_web_model.dart';
import 'package:flutter_dmzj/models/comic/chapter_info.dart';
import 'package:flutter_dmzj/models/comic/comic_related_model.dart';
import 'package:flutter_dmzj/models/comic/detail_info.dart';
import 'package:flutter_dmzj/models/comic/detail_model.dart';
import 'package:flutter_dmzj/models/comic/detail_v1_model.dart';
import 'package:flutter_dmzj/models/comic/rank_item_model.dart';
import 'package:flutter_dmzj/models/comic/recommend_model.dart';
import 'package:flutter_dmzj/models/comic/search_item.dart';
import 'package:flutter_dmzj/models/comic/search_model.dart';
import 'package:flutter_dmzj/models/comic/special_model.dart';
import 'package:flutter_dmzj/models/comic/update_item_model.dart';
import 'package:flutter_dmzj/models/comic/view_point_model.dart';
import 'package:flutter_dmzj/models/comic/web_search_model.dart';
import 'package:flutter_dmzj/models/db/download_status.dart';
import 'package:flutter_dmzj/requests/common/http_client.dart';
import 'package:flutter_dmzj/services/comic_download_service.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import '../models/comic/special_detail_model.dart';
class ComicRequest {
/// 漫画-推荐
Future<List<ComicRecommendModel>> recommend() async {
var list = <ComicRecommendModel>[];
var result = await HttpClient.instance.getJson('/comic/recommend/index');
for (var item in result) {
list.add(ComicRecommendModel.fromJson(item));
}
return list;
}
/// 猜你喜欢
Future<List<ComicRecommendItemModel>> refreshRecommend(int categoryId,
{int page = 1, int size = 3}) async {
var result = await HttpClient.instance.getJson(
'/comic/recommend/more',
queryParameters: {"cateId": categoryId, "size": size, "page": page},
);
List<ComicRecommendItemModel> list = [];
for (var item in result["data"]["recommendList"]) {
list.add(ComicRecommendItemModel.fromJson(item));
}
return list;
}
/// 首页-我的订阅
Future<ComicRecommendModel> recommendSubscribe() async {
var result = await HttpClient.instance.getJson(
'/comic/sub/list',
needLogin: true,
checkCode: true,
queryParameters: {"status": 0, "firstLetter": "", "page": 1, "size": 3},
);
var list = <ComicRecommendItemModel>[];
for (var item in result["subList"]) {
list.add(ComicRecommendItemModel.fromJson(item));
}
return ComicRecommendModel(
categoryId: 49,
title: "我的订阅",
sort: 0,
data: list,
);
}
/// 最近更新
Future<List<ComicUpdateItemModel>> latest(
{required int type, int page = 1}) async {
var result = await HttpClient.instance.getJson(
'/comic/update/list/$type/$page',
needLogin: true,
);
var list = <ComicUpdateItemModel>[];
for (var item in result["data"]) {
list.add(ComicUpdateItemModel.fromJson(item));
}
return list;
}
/// 分类
Future<List<ComicCategoryItemModel>> categores() async {
var list = <ComicCategoryItemModel>[];
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {"source": 1},
checkCode: true,
);
for (var item in result["cateList"]) {
list.add(ComicCategoryItemModel.fromJson(item));
}
// 百合赛高
list.add(ComicCategoryItemModel(tagId: 3243, title: "ゆり", cover: ""));
return list;
}
/// 分类-筛选
Future<List<ComicCategoryFilterModel>> categoryFilter() async {
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {"source": 1},
checkCode: true,
);
// for (var item in result["cateList"]) {
// list.add(ComicCategoryFilterModel.fromJson(item));
// }
var list = <ComicCategoryFilterItemModel>[];
for (var item in result["cateList"]) {
list.add(ComicCategoryFilterItemModel.fromJson(item));
}
return [
ComicCategoryFilterModel(title: "全部分类", items: list),
];
}
/// 分类下漫画
/// - [ids] 标签
/// - [sort] 排序,0=人气,1=更新
/// - [page] 页数从0开始
Future<List<ComicCategoryComicModel>> categoryComic({
required int id,
int sort = 1,
int page = 1,
int status = 0,
}) async {
var list = <ComicCategoryComicModel>[];
var result = await HttpClient.instance.getJson(
'/comic/filter/list',
queryParameters: {
"theme": id,
"status": 0,
"sortType": sort,
"page": page,
"size": 20,
},
checkCode: true,
needLogin: true // 登录可以更多内容
);
for (var item in result["comicList"]) {
list.add(ComicCategoryComicModel.fromJson(item));
}
return list;
}
/// 排行榜
Future<List<ComicRankListItemModel>> rank({
required int tagId,
required byTime,
required rankType,
int page = 1,
}) async {
var result = await HttpClient.instance.getJson(
'/comic/rank/list',
queryParameters: {
'tag_id': tagId,
'by_time': byTime,
'rank_type': rankType,
'page': page
},
);
var list = <ComicRankListItemModel>[];
for (var item in result["data"]) {
list.add(ComicRankListItemModel.fromJson(item));
}
return list;
}
/// 排行榜-分类
Future<Map<int, String>> rankFilter() async {
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {"source": 1},
checkCode: true,
);
Map<int, String> map = {
0: "全部分类",
3243: "ゆり"
};
for (var item in result["cateList"]) {
map.addAll({
item["tagId"]: item["title"],
});
}
return map;
}
/// 专题
Future<List<ComicSpecialModel>> special({int page = 1}) async {
var list = <ComicSpecialModel>[];
var result = await HttpClient.instance.getJson(
'/subject/0/$page.json',
checkCode: true,
);
for (var item in result) {
list.add(ComicSpecialModel.fromJson(item));
}
return list;
}
/// 专题
Future<ComicSpecialDetailModel> specialDetail({required int id}) async {
var result = await HttpClient.instance.getJson(
'/subject/$id.json',
checkCode: true,
);
return ComicSpecialDetailModel.fromJson(result);
}
/// 作者详情
Future<ComicAuthorModel> authorDetail({required int id}) async {
var result = await HttpClient.instance.getJson(
'/UCenter/author/$id.json',
);
return ComicAuthorModel.fromJson(result);
}
/// 作品相关
Future<ComicRelatedModel> related({required int id}) async {
var result = await HttpClient.instance.getJson(
'/v3/comic/related/$id.json',
);
return ComicRelatedModel.fromJson(result);
}
Future<ComicDetailInfo> comicDetail(
{required int comicId, bool priorityV1 = false}) async {
ComicDetailInfo info;
var errorMsg = "";
try {
if (priorityV1) {
info = ComicDetailInfo.fromV1(await comicDetailV1(comicId: comicId),
isHide: true);
} else {
info = ComicDetailInfo.fromV4(await comicDetailV4(comicId: comicId));
}
} catch (e) {
errorMsg += "${priorityV1 ? "V1" : "V4"}$e";
try {
if (priorityV1) {
info = ComicDetailInfo.fromV4(await comicDetailV4(comicId: comicId));
} else {
info = ComicDetailInfo.fromV1(await comicDetailV1(comicId: comicId),
isHide: e.toString() == "漫画不存在");
}
} catch (e) {
errorMsg += "\n${priorityV1 ? "V4" : "V1"}$e";
throw AppError("ComicID:$comicId\n无法读取漫画信息,可能需要登录或有等级限制\n$errorMsg");
}
}
return info;
}
/// 漫画详情
Future<ComicDetailModel> comicDetailV4({
required int comicId,
}) async {
var result = await HttpClient.instance.getJson(
'/comic/detail/$comicId',
needLogin: true,
checkCode: true,
);
return ComicDetailModel.fromJson(result);
}
/// 漫画详情
Future<ComicDetailV1Model> comicDetailV1({
required int comicId,
}) async {
var result = await HttpClient.instance.getJson(
'/dynamic/comicinfo/$comicId.json',
baseUrl: "https://api.dmzj.com",
needLogin: true,
);
var data = json.decode(result);
if (data["result"] != 1) {
throw AppError(data["msg"]);
}
if (data["data"]?["info"]?["id"] == null) {
throw AppError("无法读取漫画信息");
}
return ComicDetailV1Model.fromJson(data["data"]);
}
/// 漫画搜索
/// - [page] 页数从0开始
/// - [keyword] 关键字
Future<List<SearchComicItem>> search(
{required String keyword, int page = 1}) async {
var list = <ComicSearchModel>[];
var result = await HttpClient.instance.getJson(
'/search/index',
queryParameters: {
"keyword": keyword,
"page": page,
"size": 20,
},
checkCode: true,
);
for (var item in result["list"]) {
list.add(ComicSearchModel.fromJson(item));
}
return list.map((e) => SearchComicItem.fromApi(e)).toList();
}
/// 漫画搜索热词
Future<Map<int, String>> searchHotWord() async {
var result = await HttpClient.instance.getJson(
'/search/hot/0.json',
);
Map<int, String> map = {};
for (var item in result) {
map.addAll({
item["id"]: item["name"],
});
}
return map;
}
/// 章节详情
Future<ComicChapterDetail> chapterDetail(
{required int comicId,
required int chapterId,
required bool useHD}) async {
ComicChapterDetail info;
try {
//查询本地是否存在
var localInfo =
ComicDownloadService.instance.box.get("${comicId}_$chapterId");
if (localInfo != null && localInfo.status == DownloadStatus.complete) {
return ComicChapterDetail.fromDownload(localInfo);
}
var v4 = await chapterDetailV4(comicId: comicId, chapterId: chapterId);
info = ComicChapterDetail.fromV4(v4, useHD);
} catch (e) {
Log.logPrint(e);
try {
var v1 = await chapterDetailWeb(comicId: comicId, chapterId: chapterId);
info = ComicChapterDetail.fromWebApi(v1);
} catch (e) {
Log.logPrint(e);
throw AppError("ComicID:$comicId ChapterID:$chapterId\n无法读取章节信息");
}
}
return info;
}
/// 章节详情-V4
Future<ComicChapterDetailModel> chapterDetailV4(
{required int comicId, required int chapterId}) async {
var result = await HttpClient.instance.getJson(
'/comic/chapter/$comicId/$chapterId',
needLogin: true,
checkCode: true,
);
var data = ComicChapterDetailModel.fromJson(result["data"]);
return data;
}
/// 章节详情-WebAPI
Future<ComicChapterDetailWebModel> chapterDetailWeb(
{required int comicId, required int chapterId}) async {
var result = await HttpClient.instance.getJson(
'/chapinfo/$comicId/$chapterId.html',
baseUrl: "https://m.idmzj.com",
needLogin: true,
);
if (result.toString().startsWith("{")) {
var data = json.decode(result);
return ComicChapterDetailWebModel.fromJson(data);
} else {
throw AppError(result);
}
}
/// 观点、吐槽
Future<List<ComicViewPointModel>> viewPoints(
{required int comicId, required int chapterId}) async {
var list = <ComicViewPointModel>[];
var result = await HttpClient.instance.getJson(
'/viewPoint/0/$comicId/$chapterId.json',
);
for (var item in result) {
list.add(ComicViewPointModel.fromJson(item));
}
return list;
}
/// 点赞观点、吐槽
Future<bool> likeViewPoint({required int comicId, required int id}) async {
await HttpClient.instance.postJson(
'/viewPoint/praise',
checkCode: true,
data: {
"sub_type": comicId,
"uid": UserService.instance.userId,
"vote_id": id,
},
);
return true;
}
/// 点赞观点、吐槽
Future<bool> sendViewPoint(
{required int comicId,
required int chapterId,
required String content,
required int page}) async {
await HttpClient.instance.postJson(
'/viewPoint/addv2',
checkCode: true,
data: {
"sub_type": comicId,
"uid": UserService.instance.userId,
"dmzj_token": UserService.instance.dmzjToken,
"page": page,
"type": 0,
"third_type": chapterId,
"content": content,
},
);
return true;
}
/// 漫画搜索-Web接口
/// - [keyword] 关键字
Future<List<SearchComicItem>> searchWeb({required String keyword}) async {
var list = <ComicWebSearchModel>[];
var result = await HttpClient.instance.getText(
'http://sacg.idmzj.com/comicsum/search.php',
baseUrl: "",
queryParameters: {
"s": keyword,
},
);
var data = jsonDecode(result.substring(20, result.lastIndexOf(';')));
for (var item in data) {
list.add(ComicWebSearchModel.fromJson(item));
}
return list.map((e) => SearchComicItem.fromWeb(e)).toList();
}
}

View File

@@ -0,0 +1,169 @@
import 'package:flutter_dmzj/app/app_error.dart';
import 'package:flutter_dmzj/models/comment/comment_item.dart';
import 'package:flutter_dmzj/models/comment/user_comment_item.dart';
import 'package:flutter_dmzj/requests/common/api.dart';
import 'package:flutter_dmzj/requests/common/http_client.dart';
import 'package:flutter_dmzj/services/user_service.dart';
import 'package:get/get.dart';
import 'package:html_unescape/html_unescape.dart';
class CommentRequest {
var unescape = HtmlUnescape();
/// 读取最新的评论
/// - [type] 类型
/// - [objId] ID
/// - [page] 页数
/// - [pageSize] 每页数量
Future<List<CommentItem>> getComment({
required int type,
required int objId,
int sort = 1,
int page = 1,
int pageSize = 30,
}) async {
List<CommentItem> ls = [];
Map result = await HttpClient.instance.getJson(
'/comment/list',
baseUrl: Api.BASE_URL,
queryParameters: {
"type": type,
"objId": objId,
"sort": sort,
"page": page - 1,
"size": pageSize,
},
);
if (result["errno"] != 0) {
throw AppError(result["errmsg"].toString());
}
if (result["data"]["commentIdList"] == null) {
return [];
}
var ids = result["data"]["commentIdList"];
var comments = result["data"]["commentList"];
for (String id in ids) {
var idSplit = id.split(",");
var item = _parseLatestCommentItem(comments, idSplit.first, type);
if (idSplit.length > 1) {
item.parents = [];
for (var id2 in idSplit.skip(1)) {
item.parents.insert(0, _parseLatestCommentItem(comments, id2, type));
}
}
if (item.id != 0) {
ls.add(item);
}
}
return ls;
}
CommentItem _parseLatestCommentItem(Map comments, String id, int type) {
if (!comments.containsKey(id)) {
return CommentItem.createEmpty();
}
var item = comments[id];
//返回的类型非常随机有时候是int有时候是string所以使用int.tryParse
return CommentItem(
type: type,
id: int.tryParse(item["id"].toString()) ?? 0,
objId: int.tryParse(item["obj_id"].toString()) ?? 0,
content: unescape.convert(item["content"].toString()),
photo: item["photo"].toString(),
createTime: int.tryParse(item["create_time"].toString()) ?? 0,
images: item["upload_images"]
.toString()
.split(",")
.where((x) => x.isNotEmpty)
.toList(),
likeAmount: (int.tryParse(item["like_amount"].toString()) ?? 0).obs,
nickname: item["nickname"].toString(),
replyAmount: int.tryParse(item["reply_amount"].toString()) ?? 0,
gender: int.tryParse(item["sex"].toString()) ?? 0,
userId: int.tryParse(item["sender_uid"].toString()) ?? 0,
originId: int.tryParse(item["origin_comment_id"].toString()) ?? 0,
);
}
/// 发表评论
/// - [objId] ID
/// - [type] 类型 ,见AppConstant
/// - [content] 内容
/// - [toCommentId] 回复评论ID
/// - [originCommentId] 原始评论ID
/// - [toUid] 回复用户
Future<bool> sendComment({
required int objId,
required int type,
required String content,
String toCommentId = "0",
String originCommentId = "0",
String toUid = "0",
}) async {
var result = await HttpClient.instance.postJson(
"/v1/$type/new/add/app",
baseUrl: Api.BASE_URL,
data: {
"obj_id": objId,
"to_comment_id": toCommentId,
"origin_comment_id": originCommentId,
"to_uid": toUid,
"sender_terminal": 1,
"content": content,
"dmzj_token": UserService.instance.dmzjToken,
"_debug": 0
},
);
if (result["code"] != 0) {
throw AppError(result["msg"].toString());
}
return true;
}
/// 评论点赞
Future<bool> likeComment({
required int commentId,
required int objId,
required int type,
}) async {
await HttpClient.instance.getJson(
"/v1/$type/like/$commentId",
baseUrl: Api.BASE_URL,
queryParameters: {
"comment_id": commentId,
"obj_id": objId,
"type": type,
},
needLogin: true,
withDefaultParameter: true,
checkCode: true,
);
return true;
}
/// 读取用户的评论
/// - [type] 类型 0=漫画1=轻小说2=新闻
/// - [uid] 用户ID
/// - [page] 页数,从0开始
Future<List<UserCommentItem>> getUserComment({
required int type,
required int uid,
int page = 0,
}) async {
List<UserCommentItem> ls = [];
var result = await HttpClient.instance.getJson(
type == 1
? '/comment/owner/1/$uid/$page.json'
: '/v3/old/comment/owner/$type/$uid/$page.json',
baseUrl: Api.BASE_URL,
withDefaultParameter: true,
needLogin: true,
);
for (var item in result) {
ls.add(UserCommentItem.fromJson(item));
}
return ls;
}
}

View 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";
}
}

View 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);
}
}

View 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("请求失败,请检查网络");
}
}
}

View File

@@ -0,0 +1,41 @@
import 'package:dio/dio.dart';
import 'package:flutter_dmzj/models/version_model.dart';
/// 通用的请求
class CommonRequest {
Future<VersionModel> checkUpdate() async {
try {
return await checkUpdateGitMirror();
} catch (e) {
return await checkUpdateJsDelivr();
}
}
/// 检查更新
Future<VersionModel> checkUpdateGitMirror() async {
var result = await Dio().get(
"https://raw.gitmirror.com/xiaoyaocz/flutter_dmzj/zaimanhua/document/app_version.json",
queryParameters: {
"ts": DateTime.now().millisecondsSinceEpoch,
},
options: Options(
responseType: ResponseType.json,
),
);
return VersionModel.fromJson(result.data);
}
/// 检查更新
Future<VersionModel> checkUpdateJsDelivr() async {
var result = await Dio().get(
"https://cdn.jsdelivr.net/gh/xiaoyaocz/flutter_dmzj@zaimanhua/document/app_version.json",
queryParameters: {
"ts": DateTime.now().millisecondsSinceEpoch,
},
options: Options(
responseType: ResponseType.json,
),
);
return VersionModel.fromJson(result.data);
}
}

View File

@@ -0,0 +1,134 @@
import 'dart:convert';
import 'package:flutter_dmzj/app/app_error.dart';
import 'package:flutter_dmzj/models/news/news_banner_model.dart';
import 'package:flutter_dmzj/models/news/news_list_item_model.dart';
import 'package:flutter_dmzj/models/news/news_stat_model.dart';
import 'package:flutter_dmzj/models/news/news_tag_model.dart';
import 'package:flutter_dmzj/requests/common/api.dart';
import 'package:flutter_dmzj/requests/common/http_client.dart';
import 'package:flutter_dmzj/services/user_service.dart';
class NewsRequest {
/// 新闻分类
Future<List<NewsTagModel>> category() async {
var list = <NewsTagModel>[];
var result = await HttpClient.instance.getJson(
'/news/category',
);
for (var item in result["data"]["cateList"]) {
list.add(NewsTagModel.fromJson(item));
}
return list;
}
/// 新闻Banner
Future<List<NewsBannerModel>> banner() async {
var list = <NewsBannerModel>[];
var result = await HttpClient.instance.getJson('/news/recommend');
for (var item in result["data"]["recommendList"]) {
list.add(NewsBannerModel.fromJson(item));
}
return list;
}
/// 读取新闻列表
/// - [id] 新闻分类ID
/// - [page] 页数从1开始
Future<List<NewsListItemModel>> getNewsList(int id, int page) async {
var result = await HttpClient.instance.getJson(
'/news/list/$id/${id == 0 ? 2 : 3}/$page',
);
if (result["errno"] != 0) {
throw AppError(result["errmsg"]);
}
var list = <NewsListItemModel>[];
for (var item in result["data"]["newsList"]) {
list.add(NewsListItemModel.fromJson(item));
}
return list;
}
/// 新闻数据
/// - [newsId] 新闻ID
Future<NewsStatModel> stat(int newsId) async {
var result = await HttpClient.instance.getJson(
'/v3/article/total/$newsId.json',
checkCode: true,
);
return NewsStatModel.fromJson(result);
}
/// 新闻点赞
/// - [newsId] 新闻ID
Future<bool> like(int newsId) async {
await HttpClient.instance.getJson(
'/article/mood/$newsId',
checkCode: true,
);
return true;
}
/// 新闻检查收藏
/// - [newsId] 新闻ID
Future<bool> checkCollect(int newsId) async {
var uid = UserService.instance.userId;
var par = {"uid": int.parse(uid), "sub_id": newsId};
var parJson = jsonEncode(par);
var sign = Api.sign(parJson, 'app_news_sub');
var result = await HttpClient.instance.postJson(
'/api/news/subscribe/check',
baseUrl: Api.BASE_URL_INTERFACE,
data: {
"parm": parJson,
"sign": sign,
},
);
return json.decode(result)["result"] == 809;
}
/// 新闻收藏
/// - [newsId] 新闻ID
Future<bool> collect(int newsId) async {
var uid = UserService.instance.userId;
var par = {"uid": int.parse(uid), "sub_id": newsId};
var parJson = jsonEncode(par);
var sign = Api.sign(parJson, 'app_news_sub');
var result = await HttpClient.instance.postJson(
'/api/news/subscribe/add',
baseUrl: Api.BASE_URL_INTERFACE,
data: {
"parm": parJson,
"sign": sign,
},
);
return json.decode(result)["result"] == 1000;
}
/// 移除收藏
/// - [newsId] 新闻ID
Future<bool> delCollect(int newsId) async {
var uid = UserService.instance.userId;
var par = {"uid": int.parse(uid), "sub_id": newsId};
var parJson = jsonEncode(par);
var sign = Api.sign(parJson, 'app_news_sub');
var result = await HttpClient.instance.postJson(
'/api/news/subscribe/del',
baseUrl: Api.BASE_URL_INTERFACE,
data: {
"parm": parJson,
"sign": sign,
},
);
return json.decode(result)["result"] == 1000;
}
}

View File

@@ -0,0 +1,240 @@
import 'package:dio/dio.dart';
import 'package:flutter_dmzj/models/novel/category_filter_model.dart';
import 'package:flutter_dmzj/models/novel/category_model.dart';
import 'package:flutter_dmzj/models/novel/category_novel_model.dart';
import 'package:flutter_dmzj/models/novel/detail_model.dart';
import 'package:flutter_dmzj/models/novel/latest_model.dart';
import 'package:flutter_dmzj/models/novel/rank_model.dart';
import 'package:flutter_dmzj/models/novel/recommend_model.dart';
import 'package:flutter_dmzj/models/novel/search_model.dart';
import 'package:flutter_dmzj/models/novel/volume_detail_model.dart';
import 'package:flutter_dmzj/requests/common/api.dart';
import 'package:flutter_dmzj/requests/common/http_client.dart';
import 'package:flutter_dmzj/services/local_storage_service.dart';
class NovelRequest {
/// 轻小说-推荐
Future<List<NovelRecommendModel>> recommend() async {
var list = <NovelRecommendModel>[];
var result =
await HttpClient.instance.getJson('/novel/recommend', checkCode: true);
for (var item in result["recommendList"]) {
list.add(NovelRecommendModel.fromJson(item));
}
return list;
}
/// 轻小说-更新
/// - [page] 页数从0开始
Future<List<NovelLatestModel>> latest({int page = 1}) async {
var list = <NovelLatestModel>[];
var result = await HttpClient.instance.getJson(
'/novel/filter/list',
queryParameters: {
//status=0&sortType=1&page=1&size=20&tagId=0
"status": 0,
"sortType": 1,
"page": page,
"size": 20,
},
checkCode: true,
);
for (var item in result["novelList"]) {
list.add(NovelLatestModel.fromJson(item));
}
return list;
}
/// 轻小说-分类
Future<List<NovelCategoryModel>> categores() async {
var list = <NovelCategoryModel>[];
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {
"source": 2,
},
checkCode: true,
);
for (var item in result["cateList"]) {
list.add(NovelCategoryModel.fromJson(item));
}
return list;
}
/// 分类-筛选
Future<List<NovelCategoryFilterModel>> categoryFilter() async {
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {"source": 2},
checkCode: true,
);
var list = <NovelCategoryFilterItemModel>[];
for (var item in result["cateList"]) {
list.add(NovelCategoryFilterItemModel.fromJson(item));
}
return [
NovelCategoryFilterModel(title: "题材", items: list),
];
}
/// 分类下漫画
/// - [cateId] 分类
/// - [sort] 排序,0=人气,1=更新
/// - [page] 页数从0开始
Future<List<NovelCategoryNovelModel>> categoryNovel({
int cateId = 0,
int status = 0,
int sort = 0,
int page = 1,
}) async {
var list = <NovelCategoryNovelModel>[];
var result = await HttpClient.instance.getJson(
'/novel/filter/list',
queryParameters: {
"tagId": cateId,
"status": 0,
"sortType": sort,
"page": page,
"size": 20,
},
checkCode: true,
);
for (var item in result["novelList"]) {
list.add(NovelCategoryNovelModel.fromJson(item));
}
return list;
}
/// 排行榜
Future<List<NovelRankModel>> rank({
required int tagId,
required sort,
int page = 0,
}) async {
var list = <NovelRankModel>[];
var result = await HttpClient.instance.getJson(
'/novel/rank/$sort/$tagId/$page.json',
);
for (var item in result) {
list.add(NovelRankModel.fromJson(item));
}
return list;
}
/// 排行榜-分类
Future<Map<int, String>> rankFilter() async {
var result = await HttpClient.instance.getJson(
'/comic/filter/category',
queryParameters: {
"source": 2,
},
checkCode: true,
);
Map<int, String> map = {};
for (var item in result["cateList"]) {
map.addAll({
item["tagId"]: item["title"],
});
}
return map;
}
/// 轻小说搜索
/// - [page] 页数从0开始
/// - [keyword] 关键字
Future<List<NovelSearchModel>> search(
{required String keyword, int page = 1}) async {
var list = <NovelSearchModel>[];
var result = await HttpClient.instance.getJson(
'/search/index',
queryParameters: {
"keyword": keyword,
"page": page,
"size": 20,
"source": 1,
},
checkCode: true,
);
for (var item in result["list"]) {
list.add(NovelSearchModel.fromJson(item));
}
return list;
}
/// 小说搜索热词
Future<Map<int, String>> searchHotWord() async {
var result = await HttpClient.instance.getJson(
'/search/hot/1.json',
);
Map<int, String> map = {};
for (var item in result) {
map.addAll({
item["id"]: item["name"],
});
}
return map;
}
/// 小说详情
Future<NovelDetailModel> novelDetail({
required int novelId,
}) async {
var result = await HttpClient.instance.getJson(
'/novel/detail/$novelId',
needLogin: true,
checkCode: true,
);
var data = NovelDetailModel.fromJson(result);
return data;
}
/// 小说章节
Future<List<NovelVolumeDetailModel>> novelChapter({
required int novelId,
}) async {
var result = await HttpClient.instance.getJson(
'/novel/chapter/$novelId',
needLogin: true,
checkCode: true,
);
var list = <NovelVolumeDetailModel>[];
for (var item in result["data"]) {
list.add(NovelVolumeDetailModel.fromJson(item));
}
return list;
}
/// 小说正文内容
/// - [volumeId] 卷ID
/// - [chapterId] 章节ID
/// - [cancel] 取消Token
/// - [cache] 是否缓存
Future<String> novelContent({
required int volumeId,
required int chapterId,
CancelToken? cancel,
bool cache = true,
}) async {
var localContent = await LocalStorageService.instance
.getNovelContent(volumeId: volumeId, chapterId: chapterId);
if (localContent != null) {
return localContent;
}
var result = await HttpClient.instance.getText(
Api.getNovelContentUrl(volumeId: volumeId, chapterId: chapterId),
baseUrl: "",
withDefaultParameter: false,
cancel: cancel,
);
if (cache) {
await LocalStorageService.instance.saveNovelContent(
volumeId: volumeId,
chapterId: chapterId,
content: result,
);
}
return result;
}
}

View File

@@ -0,0 +1,349 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter_dmzj/app/app_constant.dart';
import 'package:flutter_dmzj/app/app_error.dart';
import 'package:flutter_dmzj/models/user/comic_history_model.dart';
import 'package:flutter_dmzj/models/user/bind_status_model.dart';
import 'package:flutter_dmzj/models/user/login_result_model.dart';
import 'package:flutter_dmzj/models/user/novel_history_model.dart';
import 'package:flutter_dmzj/models/user/subscribe_comic_model.dart';
import 'package:flutter_dmzj/models/user/subscribe_news_model.dart';
import 'package:flutter_dmzj/models/user/subscribe_novel_model.dart';
import 'package:flutter_dmzj/models/user/user_profile_model.dart';
import 'package:flutter_dmzj/requests/common/api.dart';
import 'package:flutter_dmzj/requests/common/http_client.dart';
import 'package:flutter_dmzj/services/db_service.dart';
import 'package:flutter_dmzj/services/user_service.dart';
class UserRequest {
/// 登录
/// - [nickname] 用户名
/// - [password] 密码
Future<LoginResultModel> login(
{required String nickname, required String password}) async {
var pwd = md5.convert(utf8.encode(password)).toString().toLowerCase();
Map<String, dynamic> data = {
"username": nickname,
"passwd": pwd,
};
var result = await HttpClient.instance.postJson(
"/login/passwd",
baseUrl: Api.BASE_URL_USER,
data: data,
formUrlEncoded: true,
checkCode: true,
);
return LoginResultModel.fromJson(result["user"]);
}
/// 用户资料
Future<UserProfileModel> userProfile() async {
var result = await HttpClient.instance.getJson(
"/UCenter/comicsv2/${UserService.instance.userId}.json",
baseUrl: Api.BASE_URL,
queryParameters: {
"dmzj_token": UserService.instance.dmzjToken,
},
withDefaultParameter: true,
);
return UserProfileModel.fromJson(result);
}
/// 获取绑定手机、设置密码状态
Future<UserBindStatusModel> isBindTelPwd() async {
var result = await HttpClient.instance.getJson(
"/account/isbindtelpwd",
baseUrl: Api.BASE_URL,
queryParameters: {
"dmzj_token": UserService.instance.dmzjToken,
},
withDefaultParameter: true,
checkCode: true,
);
return UserBindStatusModel.fromJson(result);
}
/// 我的漫画订阅
/// - [page] 页数从0开始
/// - [subType] 全部=1未读=2已读=3完结=4
/// - [letter] all=全部
Future<List<UserSubscribeComicItemModel>> comicSubscribes(
{required int subType, int page = 1, String letter = ""}) async {
var list = <UserSubscribeComicItemModel>[];
var result = await HttpClient.instance.getJson(
'/comic/sub/list',
queryParameters: {
//uid=$uid&sub_type=$subType&letter=$letter&dmzj_token=$token&page=$page&type=$type
"status": subType,
"firstLetter": letter,
"page": page,
"size": 20
},
needLogin: true,
checkCode: true,
);
for (var item in result["subList"]) {
list.add(UserSubscribeComicItemModel.fromJson(item));
}
return list;
}
/// 我的小说订阅
/// - [page] 页数从0开始
/// - [subType] 全部=1未读=2已读=3完结=4
/// - [letter] all=全部
Future<List<UserSubscribeNovelModel>> novelSubscribes(
{required int subType, int page = 0, String letter = "all"}) async {
var list = <UserSubscribeNovelModel>[];
var result = await HttpClient.instance.getJson(
'/novel/sub/list',
queryParameters: {
//uid=$uid&sub_type=$subType&letter=$letter&dmzj_token=$token&page=$page&type=$type
"status": subType,
"firstLetter": letter,
"page": page,
"size": 20
},
needLogin: true,
checkCode: true,
);
for (var item in result["subList"]) {
list.add(UserSubscribeNovelModel.fromJson(item));
}
return list;
}
/// 我的新闻收藏
/// - [page] 页数从0开始
Future<List<UserSubscribeNewsModel>> newsSubscribes({int page = 1}) async {
var uid = UserService.instance.userId;
var par = {"uid": int.parse(uid), "page": page};
var parJson = jsonEncode(par);
var sign = Api.sign(parJson, 'app_news_sub');
var result = await HttpClient.instance.postJson(
'/api/news/getSubscribe',
baseUrl: Api.BASE_URL_INTERFACE,
data: {
"parm": parJson,
"sign": sign,
},
);
var data = json.decode(result);
if (data["result"] != 1000) {
throw AppError(data["msg"]);
}
var list = <UserSubscribeNewsModel>[];
for (var item in data["data"]) {
list.add(UserSubscribeNewsModel.fromJson(item));
}
return list;
}
/// 添加订阅
/// - [type] 类型对应AppConstant
Future<bool> addSubscribe({required List<int> ids, required int type}) async {
var requestUrl = "/comic/sub/add";
var requestQuery = <String, dynamic>{};
if (type == AppConstant.kTypeComic) {
requestUrl = "/comic/sub/add";
requestQuery = {
"comic_id": ids.join(","),
};
} else if (type == AppConstant.kTypeNovel) {
requestUrl = "/novel/sub/add";
requestQuery = {
"novel_id": ids.join(","),
};
}
await HttpClient.instance.getJson(
requestUrl,
queryParameters: requestQuery,
needLogin: true,
checkCode: true,
);
return true;
}
/// 更新订阅的阅读状态
/// - [type] 类型对应AppConstant
Future<bool> subscribeRead({required int id, required int type}) async {
var typeStr = "mh";
if (type == AppConstant.kTypeComic) {
typeStr = "mh";
} else if (type == AppConstant.kTypeNovel) {
typeStr = "xs";
}
await HttpClient.instance.getJson(
'/subscribe/read',
queryParameters: {
"obj_id": id,
"type": typeStr,
},
withDefaultParameter: true,
needLogin: true,
);
return true;
}
/// 取消订阅
/// - [type] 类型对应AppConstant
Future<bool> removeSubscribe(
{required List<int> ids, required int type}) async {
var requestUrl = "/comic/sub/del";
var requestQuery = <String, dynamic>{};
if (type == AppConstant.kTypeComic) {
requestUrl = "/comic/sub/del";
requestQuery = {
"comic_id": ids.join(","),
};
} else if (type == AppConstant.kTypeNovel) {
requestUrl = "/novel/sub/del";
requestQuery = {
"novel_id": ids.join(","),
};
}
await HttpClient.instance.getJson(
requestUrl,
queryParameters: requestQuery,
needLogin: true,
checkCode: true,
);
return true;
}
/// 查询订阅状态
/// - [objId] 漫画ID或小说ID
/// - [type] 类型对应AppConstant
Future<bool> checkSubscribeStatus(
{required int objId, required int type}) async {
var typeId = 1;
if (type == AppConstant.kTypeComic) {
typeId = 1;
} else if (type == AppConstant.kTypeNovel) {
typeId = 2;
}
var result = await HttpClient.instance.getJson(
'/comic/sub/checkIsSub',
checkCode: true,
queryParameters: {
"objId": objId,
"source": typeId,
},
needLogin: true,
);
return result["isSub"];
}
/// 漫画阅读记录
/// - [page] 页数从0开始接口并没有分页
Future<List<UserComicHistoryModel>> comicHistory({int page = 0}) async {
var list = <UserComicHistoryModel>[];
var result = await HttpClient.instance.getJson(
'/api/getReInfo/comic/${UserService.instance.userId}/$page',
queryParameters: {},
baseUrl: Api.BASE_URL_INTERFACE,
);
var data = json.decode(result);
for (var item in data) {
list.add(UserComicHistoryModel.fromJson(item));
}
//远程与本地同步
DBService.instance.syncRemoteComicHistory(list);
return list;
}
/// 小说阅读记录
/// - [page] 页数从0开始接口并没有分页
Future<List<UserNovelHistoryModel>> novelHistory({int page = 0}) async {
var list = <UserNovelHistoryModel>[];
var result = await HttpClient.instance.getJson(
'/api/getReInfo/novel/${UserService.instance.userId}/$page',
queryParameters: {},
baseUrl: Api.BASE_URL_INTERFACE,
);
var data = json.decode(result);
for (var item in data) {
list.add(UserNovelHistoryModel.fromJson(item));
}
//远程与本地同步
DBService.instance.syncRemoteNovelHistory(list);
return list;
}
/// 上传漫画记录
Future<bool> uploadComicHistory({
required int comicId,
required int chapterId,
required int page,
required DateTime time,
}) async {
var data = {
comicId.toString(): chapterId.toString(),
"comicId": comicId.toString(),
"chapterId": chapterId.toString(),
"page": page,
"time": (time.millisecondsSinceEpoch ~/ 1000).toString()
};
await HttpClient.instance.getJson(
"/api/record/getRe",
baseUrl: Api.BASE_URL_INTERFACE,
queryParameters: {
"st": "comic",
"uid": UserService.instance.userId,
"callback": "record_jsonpCallback",
"type": 3,
"json": "[${json.encode(data)}]",
},
withDefaultParameter: true,
checkCode: true,
);
return true;
}
/// 上传小说记录
Future<bool> uploadNovelHistory({
required int novelId,
required int chapterId,
required int volumeId,
required int page,
required int total,
required DateTime time,
}) async {
var data = {
novelId.toString(): chapterId.toString(),
"lnovel_id": novelId.toString(),
"volume_id": volumeId.toString(),
"chapterId": chapterId.toString(),
"total_num": total,
"page": page,
"time": (time.millisecondsSinceEpoch ~/ 1000).toString()
};
await HttpClient.instance.getJson(
"/api/record/getRe",
baseUrl: Api.BASE_URL_INTERFACE,
queryParameters: {
"st": "novel",
"uid": UserService.instance.userId,
"callback": "record_jsonpCallback",
"type": 3,
"json": "[${json.encode(data)}]",
},
withDefaultParameter: true,
checkCode: true,
);
return true;
}
}