init
This commit is contained in:
@@ -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 = '任务被丢弃'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user