From 878657ad4a749ac278c6a64c00bc7bf315bbef12 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Mon, 5 Feb 2024 06:19:24 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20713:=20=E8=A1=8C=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=82=AF=E6=A8=AA=E5=B1=95=E9=96=8B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3471: 行ロック横展開3](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3471) - 以下のリポジトリのメソッドに行ロックを追加しました。 - tasks - checkout - タスク、チェックアウト情報にロックを追加して途中で変更できないようにする - checkin - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする - cancel - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする - suspend - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする - backup - タスクのチェックにロックを追加してタスクのステータスを途中で変更できないようにする - create - タスクのチェックにロックを追加してJobNumberのチェックで重複しないようにする - changeCheckoutPermission - タスク、チェックアウト候補のチェックにロックを追加して途中で変更されないようにする - autoRouting - 処理中にワークフロー・ワークタイプの取得にロックを追加して意図しない対象のでチェックアウト権限が作成されないようにする - ワークタイプ・ワークフローの更新/削除時にもロックを追加 こちらの資料を参考に対応しています。 [行ロックに関する影響調査](https://ndstokyo.sharepoint.com/:x:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E8%A1%8C%E3%83%AD%E3%83%83%E3%82%AF%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E5%BD%B1%E9%9F%BF%E8%AA%BF%E6%9F%BB.xlsx?d=wdd6f3d97f7b04a538095c459f8eee2eb&csf=1&web=1&e=9M43di) 対応箇所について以下にまとめました。 [Task3471](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3471?csf=1&web=1&e=wptJqD) ## レビューポイント - 対応箇所は適切でしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- .../db/migrations/056-add_tasks_index.sql | 27 +++++ .../tasks/tasks.repository.service.ts | 103 +++++++++++------- .../worktypes/worktypes.repository.service.ts | 1 + 3 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 dictation_server/db/migrations/056-add_tasks_index.sql diff --git a/dictation_server/db/migrations/056-add_tasks_index.sql b/dictation_server/db/migrations/056-add_tasks_index.sql new file mode 100644 index 0000000..983ea67 --- /dev/null +++ b/dictation_server/db/migrations/056-add_tasks_index.sql @@ -0,0 +1,27 @@ +-- +migrate Up +ALTER TABLE `tasks` ADD INDEX `idx_tasks_audio_file_id` (audio_file_id); +ALTER TABLE `tasks` ADD INDEX `idx_tasks_status` (status); +ALTER TABLE `tasks` ADD INDEX `idx_tasks_typist_user_id` (typist_user_id); +ALTER TABLE `tasks` ADD INDEX `idx_tasks_is_job_number_enabled` (is_job_number_enabled); +ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_task_id` (task_id); +ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_group_id` (user_group_id); +ALTER TABLE `checkout_permission` ADD INDEX `idx_checkout_permission_user_id` (user_id); +ALTER TABLE `users` ADD INDEX `idx_users_role` (role); +ALTER TABLE `users` ADD INDEX `idx_users_author_id` (author_id); +ALTER TABLE `users` ADD INDEX `idx_users_deleted_at` (deleted_at); +ALTER TABLE `worktypes` ADD INDEX `idx_worktypes_custom_worktype_id` (custom_worktype_id); +ALTER TABLE `workflows` ADD INDEX `idx_workflows_account_id` (account_id); + +-- +migrate Down +ALTER TABLE `tasks` DROP INDEX `idx_tasks_audio_file_id`; +ALTER TABLE `tasks` DROP INDEX `idx_tasks_status`; +ALTER TABLE `tasks` DROP INDEX `idx_tasks_typist_user_id`; +ALTER TABLE `tasks` DROP INDEX `idx_tasks_is_job_number_enabled`; +ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_task_id`; +ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_group_id`; +ALTER TABLE `checkout_permission` DROP INDEX `idx_checkout_permission_user_id`; +ALTER TABLE `users` DROP INDEX `idx_users_role`; +ALTER TABLE `users` DROP INDEX `idx_users_author_id`; +ALTER TABLE `users` DROP INDEX `idx_users_deleted_at`; +ALTER TABLE `worktypes` DROP INDEX `idx_worktypes_custom_worktype_id`; +ALTER TABLE `workflows` DROP INDEX `idx_workflows_account_id`; \ No newline at end of file diff --git a/dictation_server/src/repositories/tasks/tasks.repository.service.ts b/dictation_server/src/repositories/tasks/tasks.repository.service.ts index 824a85c..a95a8ab 100644 --- a/dictation_server/src/repositories/tasks/tasks.repository.service.ts +++ b/dictation_server/src/repositories/tasks/tasks.repository.service.ts @@ -189,6 +189,7 @@ export class TasksRepositoryService { audio_file_id: audio_file_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!task) { throw new TasksNotFoundError( @@ -204,6 +205,7 @@ export class TasksRepositoryService { typist_user_id: user_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (tasks.length > 0) { @@ -242,6 +244,7 @@ export class TasksRepositoryService { }, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // ユーザーの所属するすべてのグループIDを列挙 const groupIds = groups.map((member) => member.user_group_id); @@ -264,6 +267,7 @@ export class TasksRepositoryService { }, ], comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); //チェックアウト権限がなければエラー @@ -334,6 +338,7 @@ export class TasksRepositoryService { audio_file_id: audio_file_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!task) { throw new TasksNotFoundError( @@ -387,6 +392,7 @@ export class TasksRepositoryService { audio_file_id: audio_file_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!task) { throw new TasksNotFoundError( @@ -462,6 +468,7 @@ export class TasksRepositoryService { audio_file_id: audio_file_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!task) { throw new TasksNotFoundError( @@ -513,6 +520,7 @@ export class TasksRepositoryService { audio_file_id: audio_file_id, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!task) { throw new TasksNotFoundError( @@ -896,6 +904,7 @@ export class TasksRepositoryService { where: { account_id: account_id, is_job_number_enabled: true }, order: { created_at: 'DESC', job_number: 'DESC' }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); let newJobNumber = '00000001'; @@ -958,30 +967,6 @@ export class TasksRepositoryService { assignees: Assignee[], ): Promise { await this.dataSource.transaction(async (entityManager) => { - // UserGroupの取得/存在確認 - const userGroupIds = assignees - .filter((x) => x.typistGroupId !== undefined) - .map((y) => { - return y.typistGroupId; - }); - const groupRepo = entityManager.getRepository(UserGroup); - const groupRecords = await groupRepo.find({ - where: { - id: In(userGroupIds), - account_id: account_id, - deleted_at: IsNull(), - }, - comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, - }); - // idはユニークであるため取得件数の一致でグループの存在を確認 - if (userGroupIds.length !== groupRecords.length) { - throw new TypistUserGroupNotFoundError( - `Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map( - (x) => x.id, - )}`, - ); - } - // Userの取得/存在確認 const typistUserIds = assignees .filter((x) => x.typistUserId !== undefined) @@ -1009,6 +994,31 @@ export class TasksRepositoryService { ); } + // UserGroupの取得/存在確認 + const userGroupIds = assignees + .filter((x) => x.typistGroupId !== undefined) + .map((y) => { + return y.typistGroupId; + }); + const groupRepo = entityManager.getRepository(UserGroup); + const groupRecords = await groupRepo.find({ + where: { + id: In(userGroupIds), + account_id: account_id, + deleted_at: IsNull(), + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + // idはユニークであるため取得件数の一致でグループの存在を確認 + if (userGroupIds.length !== groupRecords.length) { + throw new TypistUserGroupNotFoundError( + `Group not exists Error. reqUserGroupId:${userGroupIds}; resUserGroupId:${groupRecords.map( + (x) => x.id, + )}`, + ); + } + // 引数audioFileIdを使ってTaskレコードを特定し、そのステータスを取得/存在確認 const taskRepo = entityManager.getRepository(Task); @@ -1024,6 +1034,7 @@ export class TasksRepositoryService { }, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); //タスクが存在しない or ステータスがUploadedでなければエラー if (!taskRecord) { @@ -1187,39 +1198,51 @@ export class TasksRepositoryService { return await this.dataSource.transaction(async (entityManager) => { // 音声ファイルを取得 const audioFileRepo = entityManager.getRepository(AudioFile); - const audioFile = await audioFileRepo.findOne({ - relations: { - task: true, - }, + const audio = await audioFileRepo.findOne({ where: { id: audioFileId, account_id: accountId, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, }); - if (!audioFile) { + if (!audio) { throw new Error( `audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`, ); } - const { task } = audioFile; - - if (!task) { - throw new Error( - `task not found. audio_file_id:${audioFileId}, accountId:${accountId}`, - ); - } // authorIdをもとにユーザーを取得 const userRepo = entityManager.getRepository(User); const authorUser = await userRepo.findOne({ where: { - author_id: audioFile.author_id, + author_id: audio.author_id, account_id: accountId, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); + // TaskとFileを取得 + const taskRepo = entityManager.getRepository(Task); + const task = await taskRepo.findOne({ + relations: { + file: true, + }, + where: { + account_id: accountId, + audio_file_id: audioFileId, + }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, + }); + + const audioFile = task?.file; + if (!audioFile) { + throw new Error( + `audio file not found. audio_file_id:${audioFileId}, accountId:${accountId}`, + ); + } + // 音声ファイル上のworktypeIdをもとにworktypeを取得 const worktypeRepo = entityManager.getRepository(Worktype); const worktypeRecord = await worktypeRepo.findOne({ @@ -1228,6 +1251,7 @@ export class TasksRepositoryService { account_id: accountId, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // 音声ファイル上のworktypeIdが設定されているが、一致するworktypeが存在しない場合はエラーを出して終了 @@ -1249,6 +1273,7 @@ export class TasksRepositoryService { worktype_id: worktypeRecord?.id ?? IsNull(), }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // Workflow(ルーティングルール)があればタスクのチェックアウト権限を設定する @@ -1276,6 +1301,7 @@ export class TasksRepositoryService { account_id: accountId, }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (!myAuthorUser) { throw new Error( @@ -1292,6 +1318,7 @@ export class TasksRepositoryService { worktype_id: worktypeRecord?.id ?? IsNull(), }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了 @@ -1358,6 +1385,7 @@ export class TasksRepositoryService { const typistUsers = await userRepo.find({ where: { account_id: accountId, id: In(typistIds) }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (typistUsers.length !== typistIds.length) { throw new Error(`typist not found. ids: ${typistIds}`); @@ -1371,6 +1399,7 @@ export class TasksRepositoryService { const typistGroups = await userGroupRepo.find({ where: { account_id: accountId, id: In(groupIds) }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); if (typistGroups.length !== groupIds.length) { throw new Error(`typist group not found. ids: ${groupIds}`); diff --git a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts index 07e1704..8ca02c9 100644 --- a/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts +++ b/dictation_server/src/repositories/worktypes/worktypes.repository.service.ts @@ -220,6 +220,7 @@ export class WorktypesRepositoryService { const worktype = await worktypeRepo.findOne({ where: { account_id: accountId, id: id }, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: 'pessimistic_write' }, }); // ワークタイプが存在しない場合はエラー if (!worktype) {