Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

搜索页面->根据全部有效书源搜索书籍及根据书源规则进行数据解析 #262

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion commons/colorLibrary/BuildProfile.ets
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
export const HAR_VERSION = '1.0.0';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
export const TARGET_NAME = 'Legado';
export const TARGET_NAME = 'default';

/**
* BuildProfile Class is used only for compatibility purposes.
Expand Down
13 changes: 13 additions & 0 deletions entry/src/main/ets/common/constants/AppPattern.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @author 2008
* @datetime 2024/8/15 13:36
* @className: AppPattern
*/
export default class AppPattern {
static readonly paramPattern: RegExp = /(?<!\{\{)[ ,]\s*(?=\{)/g;
static readonly pattern: RegExp = /\s*,\s*(?=\{)/g;
static readonly pagePattern:RegExp = /<(.*)>/;
static readonly jsPattern: RegExp = /<js>(.*?)<\/js>|@js:(.*)/i;
static readonly EXP_PATTERN: RegExp = /\{\{([^\}]*?)\}\}/g;
static readonly imgPattern: RegExp = /<img[^>]*src="([^"]*(?:"[^>]+\\})?)"[^>]*>/g;
}
59 changes: 59 additions & 0 deletions entry/src/main/ets/common/model/SearchConfig.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @author 2008
* @datetime 2024/8/15 0:06
* @className: SearchConfig
*/
export class SearchConfig {
path: string;
method: string;
body: string;
charset: string;
url: string;
webView:boolean;

constructor(path: string, method: string, body: string, charset: string, url: string, webView:boolean) {
this.path = path;
this.method = method.toUpperCase(); // 确保方法为大写
this.body = body;
this.charset = charset??'UTF-8';
this.url = url;
this.webView = webView
}

// 替换 body 中的关键字
setKeyword(keyword: string): void {
this.body = this.body.replace('{{key}}', encodeURIComponent(keyword));
}
}

interface SearchConfigData {
method: string;
body?: string;
charset?: string;
url: string;
webView?:boolean;
}

export function parseSearchString(searchString: string): SearchConfig {
// 拆分字符串,获取路径与配置
const splitIndex = searchString.indexOf(',');
const path = searchString.substring(0, splitIndex).trim();
const configString = searchString.substring(splitIndex + 1).trim();

// 对 JSON 配置进行清理和格式化
const cleanConfigString = configString
.replace(/(\w+)\s*:/g, '"$1":') // 给键加上引号
.replace(/'/g, '"'); // 将单引号替换为双引号

// 解析 JSON 配置
const config: SearchConfigData = JSON.parse(cleanConfigString);

return new SearchConfig(
path,
config.method??'GET',
config.body??'',
config.charset??'UTF-8',
config.url??'',
config.webView??false
);
}
80 changes: 80 additions & 0 deletions entry/src/main/ets/common/model/XmlAnalysis.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { SearchRule } from '../../database/entities/rule';
import { SearchBook } from '../../database/entities/SearchBook';
import { BookSource } from '../../database/entities/BookSource';
import { ExploreQuery } from '../../database/types/BookSourceType';
import axios, { AxiosError, AxiosResponse } from '@ohos/axios';
import { isNetworkUrl } from '../utils/utils';


export interface IXmlAnalysis {
baseUrl: string //搜索地址
body:string //请求内容
bookSource:BookSource//书源
variable?:string
searchUrl:string
}
interface dataRule{
body:string
rule:SearchRule
searchUrl:string
}
export class XmlAnalysisData {
baseUrl: string = '' //搜索地址
body:string = ''//请求内容
bookSource?:BookSource//书源
variable?:string
searchUrl:string = ''
constructor(xmlDate?: IXmlAnalysis) {
this.baseUrl = xmlDate?.baseUrl || ''
this.body = xmlDate?.body || ''
this.bookSource = xmlDate?.bookSource
this.variable = xmlDate?.variable
this.searchUrl = xmlDate?.searchUrl || ''
}
}
export class XmlAnalysis {
xmlDate:XmlAnalysisData = new XmlAnalysisData()
url:string = ''
source?:SearchRule
searchUrl:string = ''
init(xmlDate:XmlAnalysisData){
this.xmlDate = xmlDate
this.url = xmlDate.searchUrl
this.source = this.xmlDate.bookSource?.ruleSearch
this.searchUrl = this.xmlDate.searchUrl
}

async getBookList(): Promise<SearchBook[]> {
let bookList:SearchBook[] = []
if (!this.source) return bookList
const data: dataRule = {
body: this.xmlDate.body,
rule: this.source,
searchUrl: this.searchUrl
}
let response: AxiosResponse<object> = await axios.post('http://legado.wisdoms.xin/analysisBook', data)
if (response.data){
bookList = (response.data as SearchBook[]).map(item => {
item.bookType = this.xmlDate.bookSource?.bookSourceType ?? 0
item.source = this.xmlDate.bookSource
if (item.coverUrl && !isNetworkUrl(item.coverUrl)) {
item.coverUrl = this.xmlDate.bookSource?.bookSourceUrl + item.coverUrl
}
return item
})
}

if (bookList.length > 0){
console.log('test')
}
return bookList
}

//解析xml
async analysisXml(): Promise<SearchBook[]> {
return await this.getBookList();
}

}
// const xmlAnalysis = new XmlAnalysis();
// export default xmlAnalysis as XmlAnalysis;
35 changes: 35 additions & 0 deletions entry/src/main/ets/common/model/analyzeRule/RuleAnalyzer.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @author 2008
* @datetime 2024/8/18 2:03
* @className: AnalyzerRule
*/
import { BookSource } from '../../../database/entities/BookSource';

export interface IRuleAnalyzer {
body:string //请求内容
bookSource:BookSource//书源
}

export class RuleAnalyzerData {
body:string = '' //请求内容
bookSource?:BookSource//书源
}

export class RuleAnalyzer {
ruleAnalyzerData:RuleAnalyzerData = new RuleAnalyzerData()
init(ruleAnalyzerData:RuleAnalyzerData){
this.ruleAnalyzerData = ruleAnalyzerData
}

getElements(rule:string):ESObject{
let result: ESObject = null
return []
}

/**
* 分解规则生成规则列表
*/
splitSourceRule(ruleStr?:string,allInOne:boolean = false){

}
}
122 changes: 122 additions & 0 deletions entry/src/main/ets/common/model/taskSearchBook.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* @author 2008
* @datetime 2024/8/16 20:16
* @className: taskSearchBook
*/
import taskPool from '@ohos.taskpool';
import { BookSource } from '../../database/entities/BookSource';
import bookSourceDao from '../../database/dao/BookSourceDao';
import { showMessage } from '../../componets/common/promptShow';
import searchUtil from '../utils/searchUtils';
import { SearchBook } from '../../database/entities/SearchBook';


class taskSearchBook{
private bookSourceParts:BookSource[] = []
MAX_THREADS:number = 5;
tasks:taskPool.Task[] = [];
taskSearchBook:SearchBook[] = []

async search(searchKey: string, returnBook: (newUrls: SearchBook[]) => void, cancel: () => void): Promise<void> {
let dataStart = Date.now();
this.tasks = [];
this.taskSearchBook = []
this.bookSourceParts = []
if (!searchKey) {
return;
}

this.bookSourceParts = await bookSourceDao.getEnabledPartByGroup();
if (!this.bookSourceParts || this.bookSourceParts.length === 0) {
showMessage('启用书源为空');
cancel()
return;
}

const numBookSources = this.bookSourceParts.length;
const numThreads = Math.min(Math.ceil(numBookSources / 20), this.MAX_THREADS);
// 创建一个回调函数
const callback = (newUrls: SearchBook[]) => {
this.taskSearchBook.push(...newUrls)
returnBook(newUrls)
};

for (let i = 0; i < numThreads; i++) {
const sourcesSlice = this.bookSourceParts.slice(
(i * numBookSources) / numThreads,
((i + 1) * numBookSources) / numThreads
);

const task = new taskPool.Task(
`搜索${i}`,
searchTask,
searchKey, sourcesSlice
);
task.onReceiveData(callback)
this.tasks.push(task);
}

try {
if (this.tasks.length > 0) {
const results = await Promise.all(this.tasks.map(async (task, index) => {
console.log(`任务执行中... ${task.name} ${task.isDone()}`);
try {
return await taskPool.execute(task, index % 3);
} catch (e) {
console.log(e.message);
return [];
}
}));
const searchBooks = results.flat();
let dataEnd = Date.now();
showMessage(`搜索中止,共搜索到${this.taskSearchBook.length}个结果` + `用时${(dataEnd - dataStart) / 1000}秒`);
cancel()
}
} catch (error) {
console.error('Error during search:', error);
cancel()
showMessage('搜索中止');
}
}

cancelAllTasks() {
if (this.tasks.length > 0) {
this.tasks.forEach((task) => {
console.log(`任务执行中 ${task.name} ${task.isDone()}`)
if (task.isDone()) {
return
}
taskPool.cancel(task)
console.log(`任务已取消 ${task.name} ${task.isDone()}`)
})
}
this.tasks = [];
this.taskSearchBook = []
this.bookSourceParts = []
}

}

@Concurrent
async function searchTask(searchKey: string, bookSource: BookSource[]): Promise<SearchBook[]> {
let searchBook:SearchBook[] = []
try {
for (const source of bookSource) {
if (taskPool.Task.isCanceled()) {
console.log('任务已取消')
return []
}
const newUrls = await searchUtil.searchData(searchKey, Date.now(), source);
if (newUrls.length > 0) {
searchBook.push(...newUrls)
}
taskPool.Task.sendData(newUrls)
}
return searchBook;
} catch (e) {
console.log(e.message);
return []
}
}
const taskSearchBooks = new taskSearchBook();
export default taskSearchBooks as taskSearchBook;
63 changes: 63 additions & 0 deletions entry/src/main/ets/common/utils/NetworkUtils.ets
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* @author 2008
* @datetime 2024/8/15 13:15
* @className: NetworkUtils
*/
import { url } from '@kit.ArkTS';
class NetworkUtils {

getAbsoluteURL(baseUrl: string, searchUrl: string): string {
try {
const absoluteUrl = url.URL.parseURL(searchUrl, baseUrl);
return absoluteUrl.href;
} catch (error) {
console.error('Failed to resolve absolute URL:', error);
return searchUrl;
}
}

async getBaseUrl(url: string): Promise<string | null> {
if (!url) return null
if(url.startsWith('http://') || url.startsWith('https://')){
let index = url.indexOf("/", 9)
if (index === -1) {
return url
} else {
return url.substring(0, index)
}
}
return null
}

checkContentType(data:string){
try {
// 尝试将数据解析为 JSON
if (typeof data === 'object') {
return true;
}
JSON.parse(data);
return true;
} catch (e) {
// 如果解析失败,检查是否存在 HTML 标签
if (/<\/?[^>]+(>|$)/.test(data)) {
return false;
} else {
// 如果既不是有效的 JSON 也不是 HTML,则返回未知类型
return null;
}
}
}

//判断类型是string还是ArrayBuffer
isString(data:string|ArrayBuffer):boolean{
return typeof data === 'string'
}

cleanUrl(url:string){
return url.replace(/^\s+|\s+$/g, '').replace(/[\r\n]+/g, '');
}


}
const networkUtil = new NetworkUtils();
export default networkUtil as NetworkUtils;
Loading
Loading