fix: stabilize rule config and cross-review backend

This commit is contained in:
wren
2026-05-11 02:03:01 +08:00
parent 900fc2e8a2
commit 32fb2a4812
14 changed files with 444 additions and 46 deletions
@@ -44,6 +44,102 @@ from fastapi_modules.fastapi_leaudit.services.impl.documentServiceImpl import Do
class CrossReviewServiceImpl(ICrossReviewService):
"""交叉评查服务实现。"""
_SCHEMA_BOOTSTRAP_STATEMENTS: tuple[str, ...] = (
"""
CREATE TABLE IF NOT EXISTS leaudit_cross_review_tasks (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
task_name VARCHAR(255) NOT NULL,
task_type VARCHAR(32) NOT NULL,
doc_type_id BIGINT,
doc_type_code VARCHAR(64),
assigner_id BIGINT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'in_progress',
create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delete_time TIMESTAMPTZ
)
""",
"CREATE INDEX IF NOT EXISTS idx_lcr_tasks_assigner_id ON leaudit_cross_review_tasks (assigner_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_tasks_status ON leaudit_cross_review_tasks (status)",
"CREATE INDEX IF NOT EXISTS idx_lcr_tasks_doc_type_id ON leaudit_cross_review_tasks (doc_type_id)",
"""
CREATE TABLE IF NOT EXISTS leaudit_cross_review_task_members (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
task_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
member_role VARCHAR(32) NOT NULL DEFAULT 'participant',
create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delete_time TIMESTAMPTZ
)
""",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_members_task_id ON leaudit_cross_review_task_members (task_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_members_user_id ON leaudit_cross_review_task_members (user_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_members_role ON leaudit_cross_review_task_members (member_role)",
"""
CREATE UNIQUE INDEX IF NOT EXISTS uq_lcr_task_members_task_user_active
ON leaudit_cross_review_task_members (task_id, user_id)
WHERE delete_time IS NULL
""",
"""
CREATE TABLE IF NOT EXISTS leaudit_cross_review_task_documents (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
task_id BIGINT NOT NULL,
document_id BIGINT NOT NULL,
audit_status INTEGER NOT NULL DEFAULT 0,
create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delete_time TIMESTAMPTZ
)
""",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_documents_task_id ON leaudit_cross_review_task_documents (task_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_documents_document_id ON leaudit_cross_review_task_documents (document_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_task_documents_task_status ON leaudit_cross_review_task_documents (task_id, audit_status)",
"""
CREATE UNIQUE INDEX IF NOT EXISTS uq_lcr_task_documents_task_document_active
ON leaudit_cross_review_task_documents (task_id, document_id)
WHERE delete_time IS NULL
""",
"""
CREATE TABLE IF NOT EXISTS leaudit_cross_review_proposals (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
task_id BIGINT NOT NULL,
document_id BIGINT NOT NULL,
rule_result_id BIGINT NOT NULL,
proposer_id BIGINT NOT NULL,
proposed_score_delta NUMERIC(10, 2) NOT NULL,
reason TEXT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delete_time TIMESTAMPTZ
)
""",
"CREATE INDEX IF NOT EXISTS idx_lcr_proposals_task_id ON leaudit_cross_review_proposals (task_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_proposals_document_id ON leaudit_cross_review_proposals (document_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_proposals_rule_result_id ON leaudit_cross_review_proposals (rule_result_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_proposals_proposer_id ON leaudit_cross_review_proposals (proposer_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_proposals_status ON leaudit_cross_review_proposals (status)",
"""
CREATE TABLE IF NOT EXISTS leaudit_cross_review_votes (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
proposal_id BIGINT NOT NULL,
voter_id BIGINT NOT NULL,
vote_type VARCHAR(16) NOT NULL,
create_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
update_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delete_time TIMESTAMPTZ
)
""",
"CREATE INDEX IF NOT EXISTS idx_lcr_votes_proposal_id ON leaudit_cross_review_votes (proposal_id)",
"CREATE INDEX IF NOT EXISTS idx_lcr_votes_voter_id ON leaudit_cross_review_votes (voter_id)",
"""
CREATE UNIQUE INDEX IF NOT EXISTS uq_lcr_votes_proposal_voter_active
ON leaudit_cross_review_votes (proposal_id, voter_id)
WHERE delete_time IS NULL
""",
)
def __init__(self):
self.DocumentService: IDocumentService = DocumentServiceImpl()
@@ -56,6 +152,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
principalUserIds = self._unique_int_list(Body.principalUserIds)
documentIds = self._unique_int_list(Body.documentIds)
await self._reset_transaction_for_write(session)
async with session.begin():
taskRow = (
await session.execute(
@@ -400,6 +497,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
if not permission.canConfirm:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, permission.reason)
await self._reset_transaction_for_write(session)
async with session.begin():
mapping = (
await session.execute(
@@ -517,6 +615,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
if Body.deductionScore > 0 and currentScore >= fullScore:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前分值已满分,不能继续加分")
await self._reset_transaction_for_write(session)
async with session.begin():
proposalRow = (
await session.execute(
@@ -576,6 +675,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
if str(proposal["status"]) in {"approved", "rejected", "cancelled"}:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前提案状态不允许继续投票")
await self._reset_transaction_for_write(session)
async with session.begin():
if voteType == "cancel":
deleted = await session.execute(
@@ -638,6 +738,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
if str(proposal["status"]) not in {"pending"}:
raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "当前提案状态不允许撤销")
await self._reset_transaction_for_write(session)
async with session.begin():
await session.execute(
text(
@@ -745,6 +846,7 @@ class CrossReviewServiceImpl(ICrossReviewService):
async with GetAsyncSession() as session:
await self._ensure_tables_ready(session)
await self._reset_transaction_for_write(session)
async with session.begin():
exists = bool(
await session.scalar(
@@ -1153,6 +1255,31 @@ class CrossReviewServiceImpl(ICrossReviewService):
"leaudit_cross_review_proposals",
"leaudit_cross_review_votes",
]
missing_tables: list[str] = []
for tableName in required:
exists = bool(
await session.scalar(
text(
"""
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = current_schema()
AND table_name = :table_name
)
"""
),
{"table_name": tableName},
)
)
if not exists:
missing_tables.append(tableName)
if missing_tables:
for statement in self._SCHEMA_BOOTSTRAP_STATEMENTS:
await session.execute(text(statement))
await session.commit()
for tableName in required:
exists = bool(
await session.scalar(
@@ -1195,6 +1322,11 @@ class CrossReviewServiceImpl(ICrossReviewService):
if not exists:
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前用户不是交叉评查任务成员")
async def _reset_transaction_for_write(self, session) -> None:
"""显式写事务前清理查询阶段开启的隐式事务。"""
if session.in_transaction():
await session.rollback()
def _unique_int_list(self, values: list[int]) -> list[int]:
"""去重并保留原顺序。"""
seen: set[int] = set()