This commit is contained in:
2025-08-19 14:33:59 +08:00
commit 56a32f8a50
336 changed files with 55317 additions and 0 deletions
@@ -0,0 +1,424 @@
import Realm from 'realm'
import { RealmBaseService } from '../base/realmBase'
import { cloneDeep, isEmpty } from 'lodash'
import { Book } from '@/define/model/book/book'
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
import { OtherData } from '@/define/enum/softwareEnum'
import { TaskModal } from '@/define/model/task'
export class TaskListService extends RealmBaseService {
static instance: TaskListService | null = null
declare realm: Realm
private constructor() {
super()
}
/**
* 获取当前实例对象,为空则创建一个新的
* @returns
*/
public static async getInstance() {
if (TaskListService.instance === null) {
TaskListService.instance = new TaskListService()
await super.getInstance()
}
await TaskListService.instance.open()
return TaskListService.instance
}
/**
* 获取任务集合
*
* 该方法根据提供的查询条件从数据库中检索任务列表,支持多种筛选条件和分页功能。
* 可通过小说名称、任务名称、状态等多个维度进行筛选,并为每个任务补充关联的小说
* 和任务信息。结果按创建时间降序排列,并根据分页参数返回指定范围的数据。
*
* 支持的筛选条件包括:
* - 小说名称(模糊匹配)
* - 小说任务名称(模糊匹配)
* - 小说任务详情名称(模糊匹配)
* - 任务名称(模糊匹配)
* - 任务类型(精确匹配)
* - 任务状态(精确匹配)
* - 任务错误信息(模糊匹配)
*
* @param {TaskModal.QueryTaskCondition} queryTaskCondition - 查询条件对象,包含筛选和分页参数
* @returns {TaskModal.TaskCollection} 包含分页信息和任务数据的集合对象
*
* @example
* // 查询与"小说项目"相关的所有失败任务,第1页,每页10条
* const condition = {
* bookName: "小说项目",
* taskStatus: BookBackTaskStatus.FAIL,
* page: 1,
* pageSize: 10
* };
* const taskCollection = taskListService.GetTaskCollection(condition);
* console.log(`共找到${taskCollection.count}条记录,当前显示${taskCollection.data.length}条`);
*/
GetTaskCollection(queryTaskCondition: TaskModal.QueryTaskCondition): TaskModal.TaskCollection {
let tasks = this.realm.objects('TaskList')
if (!isEmpty(queryTaskCondition.bookName)) {
let book = this.realm
.objects('Book')
.filtered('name CONTAINS[c] $0', queryTaskCondition.bookName)
let ids = [] as string[]
if (book.length > 0) {
ids = book.map((item) => {
return item.id as string
})
}
tasks = tasks.filtered('bookId in $0', ids)
}
if (!isEmpty(queryTaskCondition.bookTaskName)) {
let bookTask = this.realm
.objects('BookTask')
.filtered('name CONTAINS[c] $0', queryTaskCondition.bookTaskName)
let ids = [] as string[]
if (bookTask.length > 0) {
ids = bookTask.map((item) => {
return item.id as string
})
}
tasks = tasks.filtered('bookTaskId in $0', ids)
}
if (!isEmpty(queryTaskCondition.bookTaskDetailName)) {
let bookTaskDetail = this.realm
.objects('BookTaskDetail')
.filtered('name CONTAINS[c] $0', queryTaskCondition.bookTaskDetailName)
let ids = [] as string[]
if (bookTaskDetail.length > 0) {
ids = bookTaskDetail.map((item) => {
return item.id as string
})
}
tasks = tasks.filtered('bookTaskDetailId in $0', ids)
}
if (!isEmpty(queryTaskCondition.taskName)) {
tasks = tasks.filtered('name CONTAINS[c] $0', queryTaskCondition.taskName)
}
if (!isEmpty(queryTaskCondition.taskType)) {
tasks = tasks.filtered('type == $0', queryTaskCondition.taskType)
}
if (!isEmpty(queryTaskCondition.taskStatus)) {
tasks = tasks.filtered('status == $0', queryTaskCondition.taskStatus)
}
if (!isEmpty(queryTaskCondition.taskErrorMessage)) {
tasks = tasks.filtered('errorMessage CONTAINS[c] $0', queryTaskCondition.taskErrorMessage)
}
let count = tasks.length
tasks = tasks.sorted('createTime', true)
let task = tasks.slice(
(queryTaskCondition.page - 1) * queryTaskCondition.pageSize,
queryTaskCondition.page * queryTaskCondition.pageSize
) as TaskModal.BackTaskCollection[]
let taskList = Array.from(task).map((item) => {
let resObj = {
...item
} as TaskModal.BackTaskCollection
return cloneDeep(resObj)
})
for (let i = 0; i < taskList.length; i++) {
const element = taskList[i]
let book = this.realm.objectForPrimaryKey('Book', element.bookId)
if (book) {
element.bookName = book.name as string
}
let bookTask = this.realm.objectForPrimaryKey('BookTask', element.bookTaskId)
if (bookTask) {
element.bookTaskName = bookTask.name as string
}
let bookTaskDetail = this.realm.objectForPrimaryKey(
'BookTaskDetail',
element.bookTaskDetailId
)
if (bookTaskDetail) {
element.bookTaskDetailName = bookTaskDetail.name as string
}
}
return {
page: queryTaskCondition.page,
pageSize: queryTaskCondition.pageSize,
count: count,
data: taskList
}
}
/**
* 获取等待状态的任务和返回指定数量的数据
* @param executeType 任务的执行类型
* @param count 返回数据的数量
*/
GetWaitTaskAndSlice(executeType: TaskExecuteType, count: number) {
try {
let tasks = this.realm
.objects<TaskModal.Task>('TaskList')
.filtered(
'(status == $0 || status == $1) && executeType == $2',
BookBackTaskStatus.WAIT,
BookBackTaskStatus.RECONNECT,
executeType ? executeType : TaskExecuteType.AUTO
)
.sorted('createTime', false)
let tasksArray = Array.from(tasks)
if (count != null) {
tasksArray = tasksArray.slice(0, count)
}
let res = tasksArray.map((item) => {
let resObj = {
...item
}
return resObj
})
return res
} catch (error) {
throw error
}
}
/**
* 获取指定状态的任务数量
*
* 该方法查询数据库中符合指定状态集合的任务总数,用于统计分析不同状态的任务分布。
* 使用Realm的IN操作符高效查询匹配多个状态条件的任务。
*
* @param {BookBackTaskStatus[]} status - 要统计的任务状态数组,如[WAIT, RUNNING]
* @returns {number} 符合指定状态的任务总数
*
* @example
* // 获取所有等待中的任务数量
* const waitingCount = taskListService.GetAssignStatusTaskCount([BookBackTaskStatus.WAIT]);
*
* // 获取所有正在运行或重连的任务数量
* const activeCount = taskListService.GetAssignStatusTaskCount([
* BookBackTaskStatus.RUNNING,
* BookBackTaskStatus.RECONNECT
* ]);
*/
GetAssignStatusTaskCount(status: BookBackTaskStatus[]): number {
let taskLength = this.realm
.objects<TaskModal.Task>('TaskList')
.filtered('status IN $0', status).length
return taskLength
}
/**
* 新增一个小说相关的后台任务队列
* @param bookBackTask 要添加的小说数据
*/
AddOneTask(
bookId: string,
taskType: BookBackTaskType,
executeType: TaskExecuteType = TaskExecuteType.AUTO,
bookTaskId: string | undefined = undefined,
bookTaskDetailId: string | undefined = undefined,
responseMessageName?: string
): TaskModal.Task {
try {
// 通过bookid获取book信息
let book = this.realm.objectForPrimaryKey('Book', bookId)
if (book == null) {
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说')
}
let bookTask
if (bookTaskId) {
bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
if (bookTask == null) {
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说批次任务')
}
}
let bookTaskDetail
if (bookTaskDetailId) {
bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
if (bookTaskDetail == null) {
throw new Error(
'新增后台队列任务到数据库失败,没有找到对应的小说批次任务详情(分镜数据)'
)
}
}
// 开始往数据库中写数据
let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${
bookTaskDetail ? bookTaskDetail.name : 'default'
}-${taskType}`
let bookBackTask = {
id: crypto.randomUUID(),
bookId: bookId,
bookTaskId: bookTaskId ? bookTaskId : OtherData.DEFAULT,
bookTaskDetailId: bookTaskDetailId ? bookTaskDetailId : OtherData.DEFAULT,
name: name,
type: taskType,
executeType: executeType,
status: BookBackTaskStatus.WAIT,
createTime: new Date(),
updateTime: new Date(),
messageName: responseMessageName,
startTime: 0,
endTime: 0
} as TaskModal.Task
this.realm.write(() => {
this.realm.create('TaskList', bookBackTask)
})
return bookBackTask
} catch (error) {
throw error
}
}
/**
* 修改一个小说相关的后台任务队列中的详细信息
* (对于后台的队列任务只能修改状态)和错误信息
* @param bookBackTask 修改的数据
*/
UpdateTaskStatus(bookBackTask: Book.UpdateBookTaskListStatus): Book.UpdateBookTaskListStatus {
try {
// 判断数据是不是存在
if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) {
throw new Error('修改后台队列任务失败,数据不完整,缺少必要字段')
}
// 开始修改
this.transaction(() => {
// 获取指定ID的队列任务
let _bookBackTask = this.realm.objectForPrimaryKey(
'TaskList',
bookBackTask.id
) as TaskModal.Task
// 判断数据是不是存在
if (_bookBackTask == null) {
throw new Error('修改后台队列任务失败,数据不存在')
}
// 修改数据
_bookBackTask.status = bookBackTask.status
if (bookBackTask.errorMessage) {
_bookBackTask.errorMessage = bookBackTask.errorMessage
}
// 根据状态修改结束和完成时间
if (bookBackTask.status == BookBackTaskStatus.RUNNING) {
_bookBackTask.startTime = new Date().getTime()
} else if (
bookBackTask.status == BookBackTaskStatus.DONE ||
bookBackTask.status == BookBackTaskStatus.FAIL ||
bookBackTask.status == BookBackTaskStatus.PAUSE
) {
_bookBackTask.endTime = new Date().getTime()
} else if (bookBackTask.status == BookBackTaskStatus.RECONNECT) {
_bookBackTask.startTime = 0
_bookBackTask.endTime = 0
}
})
return bookBackTask
} catch (error) {
throw error
}
}
/**
* 删除满足条件的数据,包含 id、bookId、bookTaskId
* 上面的条件,至少要有一个
* @param bookBackTask 删除的数据
*/
async DeleteBookBackTask(bookBackTask) {
try {
if (
!bookBackTask.hasOwnProperty('id') &&
!bookBackTask.hasOwnProperty('bookId') &&
!bookBackTask.hasOwnProperty('bookTaskId')
) {
throw new Error('删除后台队列任务失败,缺少必要的删除条件')
}
this.transaction(() => {
// 构建查询条件
const tasksToDelete = this.realm.objects('TaskList').filtered('id == $0', bookBackTask.id)
if (bookBackTask.bookId) {
tasksToDelete.filtered('bookId == $0', bookBackTask.bookId)
}
if (bookBackTask.bookTaskId) {
tasksToDelete.filtered('bookTaskId == $0', bookBackTask.bookTaskId)
}
this.realm.delete(tasksToDelete)
return bookBackTask
})
} catch (error) {
throw error
}
}
/**
* 设置指定消息名称的任务为失败,并设置错误消息
* @param messageName
* @param errorMessage
*/
async SetMessageNameTaskToFail(messageName: string, errorMessage: string) {
let tasks = this.realm.objects('TaskList').filtered('messageName == $0 ', messageName)
tasks = tasks.filtered('status != $0', BookBackTaskStatus.DONE)
tasks = tasks.filtered('status != $0', BookBackTaskStatus.FAIL)
let ids = tasks.map((item) => {
return item.id
})
this.transaction(() => {
for (let i = 0; i < ids.length; i++) {
let task = this.realm.objectForPrimaryKey('TaskList', ids[i])
if (task == null) {
throw new Error('没有找到对应的任务')
}
task.status = BookBackTaskStatus.FAIL
task.errorMessage = errorMessage
}
})
}
/**
* 丢弃所有未开始的后台任务
*
* 该方法将所有处于等待(WAIT)或重新连接(RECONNECT)状态的任务标记为失败(FAIL),
* 并设置错误信息为"任务被丢弃"。通常在需要清理任务队列、重启系统或手动干预任务处理
* 流程时使用。
*
* 丢弃操作会在一个数据库事务中执行,确保所有状态更改的原子性,避免部分更新导致的
* 数据不一致问题。
*
* @returns {Promise<void>} 无返回值的Promise
* @throws {Error} 当任务ID存在但无法找到对应任务对象时抛出错误
*
* @example
* // 重启任务队列前丢弃所有未开始的任务
* await taskListService.GiveUpNotStartBackTask();
* await taskManager.restart();
*/
GiveUpNotStartBackTask(): void {
let task = this.realm
.objects('TaskList')
.filtered(
'status == $0 || status == $1',
BookBackTaskStatus.WAIT,
BookBackTaskStatus.RECONNECT
)
let ids = task.map((item) => {
return item.id
})
// 讲所有的人物状态改为放弃,然后errmessage改为 此任务已丢弃
this.transaction(() => {
for (let i = 0; i < ids.length; i++) {
const element = this.realm.objectForPrimaryKey('TaskList', ids[i])
if (element == null) {
throw new Error('没有找到对应的任务')
}
element.status = BookBackTaskStatus.FAIL
element.errorMessage = '任务被丢弃'
}
})
}
}