-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #262 from mgz0227/dev_2008
搜索页面->根据全部有效书源搜索书籍及根据书源规则进行数据解析
- Loading branch information
Showing
22 changed files
with
1,019 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
35
entry/src/main/ets/common/model/analyzeRule/RuleAnalyzer.ets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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){ | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.