初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
import 'package:dio/dio.dart';
import 'package:hgdj/hj_model/drama/drama_models.dart';
import 'package:hgdj/hj_model/drama_media_info.dart';
import 'package:hgdj/hj_model/list_base_model.dart';
import 'package:hgdj/hj_model/media_content.dart';
import 'package:hgdj/tools_base/net/base_resp_bean.dart';
import 'package:hgdj/tools_base/net/http_manager.dart';
/// 短剧接口。金币解锁已并入 PayManager/BuyService,这里只剩读操作 + 两个计数型写操作:
/// 信息流翻页去重、分享互动计数,都必须带 `X-Request-ID`——同一次业务的重试要复用同一个 id,
/// 换新 id 就会重复计数
class DramaService {
static Options _idempotent(String requestId) =>
Options(headers: {'X-Request-ID': requestId});
/// AI短剧信息流:一条 = 一部剧 + 该剧第 1 集。
/// 排序、去重、已看沉底全在服务端,客户端不得二次排序,也不分页页码——每次请求都是下一批
static Future<ListBaseModel<DramaFeedItem>?> fetchFeed(
{int size = 20, String? requestId}) async {
final result = await httpManager.fetchResponseByGET(
'/media/drama/feed',
param: {'pageSize': size},
options: requestId == null ? null : _idempotent(requestId),
jsonTransformation: (json) => ListBaseModel<DramaFeedItem>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 短剧专题列表(热门短剧 Tab,按 sort 倒序已由服务端排好)
static Future<List<DramaTopic>?> fetchTopics() async {
final result = await httpManager.fetchResponseByGET(
'/media/drama/topics',
jsonTransformation: (json) =>
(json['list'] as List?)
?.map((e) => DramaTopic.fromJson(e))
.toList() ??
<DramaTopic>[],
);
return result.isSuccess ? result.data : null;
}
/// 专题作品列表(热门短剧橱窗)
static Future<ListBaseModel<DramaMediaInfo>?> fetchTopicWorks(
String topicId, {
int page = 1,
int size = 20,
}) async {
if (topicId.isEmpty) return null;
final result = await httpManager.fetchResponseByGET(
'/media/drama/topic/works',
param: {
'topicId': topicId,
'pageNumber': page,
'pageSize': size,
},
jsonTransformation: (json) =>
ListBaseModel<DramaMediaInfo>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 搜索短剧:`GET /media/search`[kind] 固定 4
/// [sortType] 1 最新上架、2 最多观看;默认 1
static Future<DramaSearchResult?> search({
required String keyword,
int page = 1,
int size = 20,
int sortType = 1,
}) async {
final result = await httpManager.fetchResponseByGET(
'/media/search',
param: {
'keyword': keyword,
'kind': 4,
'pageNumber': page,
'pageSize': size,
'sortType': sortType,
},
jsonTransformation: (json) => DramaSearchResult.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 短剧详情(剧信息 + resume 续播位置,不含分集)
static Future<DramaMediaInfo?> fetchDetail(String? dramaId) async {
if (dramaId?.isNotEmpty != true) return null;
final result = await httpManager.fetchResponseByGET(
'/media/info',
param: {'id': dramaId},
jsonTransformation: (json) => DramaMediaInfo.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 短剧片库搜索:标签页用它按标签取剧,走的是和 ACG 片库同一个接口。
/// [tagIds] 空/不传 = 不按标签筛;[sortType] 1热门推荐 2最新上架 4最多收藏
static Future<ListBaseModel<DramaMediaInfo>?> searchLibrary({
int page = 1,
int size = 20,
List<String>? tagIds,
int? sortType,
}) async {
final param = <String, dynamic>{
'pageNumber': page,
'pageSize': size,
'mediaType': 'drama',
'sortType': sortType,
'tagIds': tagIds,
}..removeWhere((k, v) => v == null || (v is List && v.isEmpty));
final result = await httpManager.fetchResponseByPOST(
'/media/library/search',
param: param,
jsonTransformation: (json) =>
ListBaseModel<DramaMediaInfo>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 分集列表(选集面板用) [sortType] 0-正序 1-倒序
static Future<ListBaseModel<MediaContent>?> fetchEpisodeList(
String dramaId, int page, int size) async {
if (dramaId.isEmpty) return null; // 与 fetchDetail 一致:没有剧 id 就别发空请求
final result = await httpManager.fetchResponseByGET(
'/media_content/list',
param: {
'mediaId': dramaId,
'pageNumber': page,
'pageSize': size,
'sortType': 0
},
jsonTransformation: (json) => ListBaseModel<MediaContent>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 分集详情:播放地址 + canPlay + 未解锁时的 paywall。
/// 列表里的播放地址会过期,每次起播都得重新拉这个接口,不能跨时长缓存
static Future<MediaContent?> fetchEpisode(String? contentId) async {
if (contentId?.isNotEmpty != true) return null;
final result = await httpManager.fetchResponseByGET(
'/media_content/info',
param: {'id': contentId},
jsonTransformation: (json) => MediaContent.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 我的喜欢-短剧
static Future<ListBaseModel<DramaMediaInfo>?> fetchLikes(int page,
{int size = 20}) async {
final result = await httpManager.fetchResponseByGET(
'/mine/like',
param: {'likeType': 'drama', 'pageNumber': page, 'pageSize': size},
jsonTransformation: (json) =>
ListBaseModel<DramaMediaInfo>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 我的收藏-短剧 [sortType] 0-最新收藏 1-最近更新
static Future<ListBaseModel<DramaMediaInfo>?> fetchFavorites(int page,
{int size = 20, int sortType = 0}) async {
final result = await httpManager.fetchResponseByGET(
'/media_bookshelf/list',
param: {
'type': 'drama',
'sortType': sortType,
'pageNumber': page,
'pageSize': size
},
jsonTransformation: (json) =>
ListBaseModel<DramaMediaInfo>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 我的已购-短剧(按剧去重,点进去看每集的 hasBuy)
static Future<ListBaseModel<DramaMediaInfo>?> fetchPurchased(int page,
{int size = 20}) async {
final result = await httpManager.fetchResponseByGET(
'/media/my_buy',
param: {'mediaType': 'drama', 'pageNumber': page, 'pageSize': size},
jsonTransformation: (json) =>
ListBaseModel<DramaMediaInfo>.fromJson(json),
);
return result.isSuccess ? result.data : null;
}
/// 短剧单集下载授权:**下载链路唯一入口**。
/// 登录态、上下架、短剧权益、钱包下载次数校验 + 扣 1 次 + 幂等,服务端一次做完并回下载地址,
/// 前端不要再调 `/mine/privilege/consume` 或 `/mine/download/use`——那两个只扣次不给资源,会重复扣。
/// [requestId] 同一次下载(含超时重试、刷新过期地址)必须复用,换新的会再扣一次;
/// 返回整个 [BaseRespBean]:失败要按 code 分流引导,不能只看有没有数据
static Future<BaseRespBean> authorizeDownload({
required String mediaId,
required String contentId,
required String requestId,
}) {
return httpManager.fetchResponseByPOST(
'/media/drama/download/authorize',
param: {'mediaId': mediaId, 'contentId': contentId},
options: _idempotent(requestId),
jsonTransformation: (json) => DramaDownloadAuth.fromJson(json),
);
}
/// 分享落地:服务端按 [eventId] 幂等累计分享互动数,返回二维码 PNG 的 base64
static Future<String?> shareOutput({
required String content,
required String? mediaId,
String? contentId,
required String eventId,
}) async {
final result = await httpManager.fetchResponseByPOST(
'/share/output',
param: {
'content': content,
'objType': 'drama',
'mediaID': mediaId,
'contentID': contentId ?? '',
'eventId': eventId,
},
options: _idempotent(eventId),
);
return result.isSuccess
? (result.data is Map ? result.data['qrCode'] : null)
: null;
}
}