From a6c3f92c1822e2ab1d1138e9ed4840d051b8902b Mon Sep 17 00:00:00 2001
From: iSecloud <869820505@qq.com>
Date: Mon, 13 Jan 2025 15:26:42 +0800
Subject: [PATCH] =?UTF-8?q?feat(backend):=20=E5=B7=A1=E6=A3=80=E9=80=9A?=
 =?UTF-8?q?=E7=94=A8=E6=A1=86=E6=9E=B6=20#9120?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 dbm-ui/backend/configuration/models/dba.py    | 19 ++++-
 dbm-ui/backend/db_report/README.md            |  4 +
 dbm-ui/backend/db_report/ReadMe.md            |  4 -
 dbm-ui/backend/db_report/apps.py              | 15 ++++
 dbm-ui/backend/db_report/enums/__init__.py    | 14 ++++
 dbm-ui/backend/db_report/filters.py           | 46 ++++++++++++
 dbm-ui/backend/db_report/mock_data.py         | 20 +++++
 dbm-ui/backend/db_report/register.py          | 34 +++++++++
 dbm-ui/backend/db_report/report_baseview.py   | 75 +++++++++++++++++--
 ...ck_report_serializer.py => serializers.py} | 12 ++-
 dbm-ui/backend/db_report/urls.py              | 29 +++----
 dbm-ui/backend/db_report/views/__init__.py    |  9 +--
 .../views/checksum_check_report_view.py       | 11 +--
 .../db_report/views/checksum_instance_view.py |  4 +-
 .../db_report/views/meta_check_view.py        | 18 ++---
 .../views/mysql/mysql_checksum_report_view.py | 23 ++++++
 .../mysql/mysql_dbmeta_check_view.py}         | 14 ++++
 .../{ => mysql}/mysqlbackup_check_view.py     | 20 ++---
 .../views/{ => redis}/dbmon_heartbeat_view.py | 16 ++--
 .../{ => redis}/redis_dbmeta_check_view.py    | 20 ++---
 .../{ => redis}/redisbackup_check_view.py     | 21 ++----
 dbm-ui/backend/ticket/builders/__init__.py    | 19 +----
 dbm-ui/backend/ticket/todos/__init__.py       | 19 +----
 dbm-ui/backend/urls.py                        |  1 +
 dbm-ui/backend/utils/register.py              | 33 ++++++++
 25 files changed, 360 insertions(+), 140 deletions(-)
 create mode 100644 dbm-ui/backend/db_report/README.md
 delete mode 100644 dbm-ui/backend/db_report/ReadMe.md
 create mode 100644 dbm-ui/backend/db_report/filters.py
 create mode 100644 dbm-ui/backend/db_report/register.py
 rename dbm-ui/backend/db_report/{serializers/meta_check_report_serializer.py => serializers.py} (71%)
 create mode 100644 dbm-ui/backend/db_report/views/mysql/mysql_checksum_report_view.py
 rename dbm-ui/backend/db_report/{serializers/__init__.py => views/mysql/mysql_dbmeta_check_view.py} (53%)
 rename dbm-ui/backend/db_report/views/{ => mysql}/mysqlbackup_check_view.py (89%)
 rename dbm-ui/backend/db_report/views/{ => redis}/dbmon_heartbeat_view.py (89%)
 rename dbm-ui/backend/db_report/views/{ => redis}/redis_dbmeta_check_view.py (89%)
 rename dbm-ui/backend/db_report/views/{ => redis}/redisbackup_check_view.py (89%)
 create mode 100644 dbm-ui/backend/utils/register.py

diff --git a/dbm-ui/backend/configuration/models/dba.py b/dbm-ui/backend/configuration/models/dba.py
index 04e0c65bf4..69afcdad52 100644
--- a/dbm-ui/backend/configuration/models/dba.py
+++ b/dbm-ui/backend/configuration/models/dba.py
@@ -8,7 +8,7 @@
 an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.
 """
-from typing import Dict, List, Union
+from typing import Dict, List, Tuple, Union
 
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
@@ -62,9 +62,24 @@ def get_biz_db_type_admins(cls, bk_biz_id: int, db_type: str) -> List[str]:
         return DEFAULT_DB_ADMINISTRATORS
 
     @classmethod
-    def get_dba_for_db_type(cls, bk_biz_id: int, db_type: str) -> List[str]:
+    def get_dba_for_db_type(cls, bk_biz_id: int, db_type: str) -> Tuple[List[str], List[str], List[str]]:
         """获取主dba、备dba、二线dba人员"""
         dba_list = cls.list_biz_admins(bk_biz_id)
         dba_content = next((dba for dba in dba_list if dba["db_type"] == db_type), {"users": []})
         users = dba_content.get("users", [])
         return users[:1], users[1:2], users[2:]
+
+    @classmethod
+    def get_manage_bizs(cls, db_type: str, username: str) -> Tuple[List[str], List[str]]:
+        """获取待我处理,待我协助的业务"""
+        manage_biz = DBAdministrator.objects.filter(db_type=db_type, users__0=username).values_list(
+            "bk_biz_id", flat=True
+        )
+
+        assist_bizs = (
+            DBAdministrator.objects.filter(db_type=db_type, users__contains=username)
+            .exclude(users__0=username)
+            .values_list("bk_biz_id", flat=True)
+        )
+
+        return list(manage_biz), list(assist_bizs)
diff --git a/dbm-ui/backend/db_report/README.md b/dbm-ui/backend/db_report/README.md
new file mode 100644
index 0000000000..92bfc8f2b2
--- /dev/null
+++ b/dbm-ui/backend/db_report/README.md
@@ -0,0 +1,4 @@
+1. 报告结果 _model_ 继承 `BaseReportABS`
+2. 报告结果视图继承 `ReportBaseViewSet`
+3. 视图
+3. 示例可以参考 `dbm-ui/backend/db_report/views/mysql/mysqlbackup_check_view.py`
\ No newline at end of file
diff --git a/dbm-ui/backend/db_report/ReadMe.md b/dbm-ui/backend/db_report/ReadMe.md
deleted file mode 100644
index d13b18391a..0000000000
--- a/dbm-ui/backend/db_report/ReadMe.md
+++ /dev/null
@@ -1,4 +0,0 @@
-1. 报告结果 _model_ 继承 `BaseReportABS`
-2. 报告结果视图继承 `ReportBaseViewSet`
-3. 在 `dbm-ui/backend/db_report/urls.py` 中注册
-4. 示例可以参考 `dbm-ui/backend/db_report/views/meta_check_view.py`
\ No newline at end of file
diff --git a/dbm-ui/backend/db_report/apps.py b/dbm-ui/backend/db_report/apps.py
index 4b98cd720f..9c076f7d77 100644
--- a/dbm-ui/backend/db_report/apps.py
+++ b/dbm-ui/backend/db_report/apps.py
@@ -1,6 +1,21 @@
+"""
+TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
+Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
+Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at https://opensource.org/licenses/MIT
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+"""
+
 from django.apps import AppConfig
 
+from backend.db_report.register import register_all_reports
+
 
 class DbReportConfig(AppConfig):
     default_auto_field = "django.db.models.BigAutoField"
     name = "backend.db_report"
+
+    def ready(self):
+        register_all_reports()
diff --git a/dbm-ui/backend/db_report/enums/__init__.py b/dbm-ui/backend/db_report/enums/__init__.py
index 220cfc1663..309183b9d7 100644
--- a/dbm-ui/backend/db_report/enums/__init__.py
+++ b/dbm-ui/backend/db_report/enums/__init__.py
@@ -19,9 +19,23 @@
 
 SWAGGER_TAG = _("巡检报告")
 
+REPORT_COUNT_CACHE_KEY = "{user}_report_count_key"
+
 
 class ReportFieldFormat(str, StructuredEnum):
     TEXT = EnumField("text", _("文本渲染"))
     STATUS = EnumField("status", _("状态渲染"))
     # 数据校验失败详情字段
     FAIL_SLAVE_INSTANCE = EnumField("fail_slave_instance", _("数据校验失败详情渲染"))
+
+
+class ReportType(str, StructuredEnum):
+    CHECKSUM = EnumField("checksum", _("数据校验"))
+    FULL_BACKUP_CHECK = EnumField("full_backup_check", _("全备校验"))
+    BINLOG_BACKUP_CHECK = EnumField("binlog_backup_check", _("集群binlog检查"))
+
+    ALONE_INSTANCE_CHECK = EnumField("alone_instance_check", _("孤立实例检查"))
+    STATUS_ABNORMAL_CHECK = EnumField("status_abnormal_check", _("实例异常状态检查"))
+    META_CHECK = EnumField("meta_check", _("元数据检查"))
+
+    REDIS_DBMON_HEARTBEAT_CHECK = EnumField("dbmon_heartbeat_check", _("dbmon心跳超时检查"))
diff --git a/dbm-ui/backend/db_report/filters.py b/dbm-ui/backend/db_report/filters.py
new file mode 100644
index 0000000000..da726bec5b
--- /dev/null
+++ b/dbm-ui/backend/db_report/filters.py
@@ -0,0 +1,46 @@
+# -*- coding:utf-8 -*-
+"""
+TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
+Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
+Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at https://opensource.org/licenses/MIT
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+"""
+from django.utils.translation import ugettext_lazy as _
+from django_filters import rest_framework as filters
+from django_filters.rest_framework import DjangoFilterBackend
+
+from backend.configuration.models import DBAdministrator
+
+
+class ReportListFilter(filters.FilterSet):
+    manage = filters.CharFilter(
+        field_name="manage", method="filter_manage", label=_("处理类型"), help_text=_("todo待我处理/assist待我协助")
+    )
+    cluster = filters.CharFilter(field_name="cluster", method="filter_cluster", label=_("集群名"))
+
+    def filter_manage(self, queryset, name, value):
+        username = self.request.user.username
+        db_type = self.request.path.strip("/").split("/")[1]
+        manage_bizs, assist_bizs = DBAdministrator.get_manage_bizs(db_type, username)
+        # 待我处理
+        if value == "todo":
+            return queryset.filter(bk_biz_id__in=manage_bizs)
+        # 待我协助
+        elif value == "assist":
+            return queryset.filter(bk_biz_id__in=assist_bizs)
+        # 其他情况忽略
+        return queryset
+
+    def filter_cluster(self, queryset, name, value):
+        cluster = value.split(",")
+        if len(cluster) == 1:
+            return queryset.filter(cluster__icontains=cluster[0])
+        else:
+            return queryset.filter(cluster__in=cluster)
+
+
+class ReportFilterBackend(DjangoFilterBackend):
+    filterset_base = ReportListFilter
diff --git a/dbm-ui/backend/db_report/mock_data.py b/dbm-ui/backend/db_report/mock_data.py
index d4907bf571..a0f2cddd62 100644
--- a/dbm-ui/backend/db_report/mock_data.py
+++ b/dbm-ui/backend/db_report/mock_data.py
@@ -144,3 +144,23 @@
         {"name": "msg", "display_name": "详情", "format": "text"},
     ],
 }
+
+REPORT_OVERVIEW_DATA = {
+    "redis": [
+        "dbmon_heartbeat_check",
+        "full_backup_check",
+        "binlog_backup_check",
+        "alone_instance_check",
+        "status_abnormal_check",
+    ],
+    "mysql": ["full_backup_check", "binlog_backup_check", "meta_check", "checksum"],
+}
+
+REPORT_COUNT_DATA = {
+    "redis": {
+        "dbmon_heartbeat_check": {"manage_count": 10896, "assist_count": 0},
+    },
+    "mysql": {
+        "full_backup_check": {"manage_count": 0, "assist_count": 26},
+    },
+}
diff --git a/dbm-ui/backend/db_report/register.py b/dbm-ui/backend/db_report/register.py
new file mode 100644
index 0000000000..9f06e0a9bf
--- /dev/null
+++ b/dbm-ui/backend/db_report/register.py
@@ -0,0 +1,34 @@
+"""
+TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
+Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
+Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at https://opensource.org/licenses/MIT
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+"""
+import logging
+import os
+from collections import defaultdict
+from typing import Dict, List
+
+from backend.utils.register import re_import_modules
+from config import BASE_DIR
+
+logger = logging.getLogger("root")
+
+db_report_maps: Dict[str, List] = defaultdict(list)
+
+
+def register_report(db_type):
+    """巡检视图的注册器"""
+
+    def decorator(report_cls):
+        db_report_maps[db_type].append(report_cls)
+
+    return decorator
+
+
+def register_all_reports():
+    """递归注册当前目录下所有的巡检报告"""
+    re_import_modules(path=os.path.join(BASE_DIR, "backend/db_report/views"), module_path="backend.db_report.views")
diff --git a/dbm-ui/backend/db_report/report_baseview.py b/dbm-ui/backend/db_report/report_baseview.py
index 6173ac5b28..ab0a0738fd 100644
--- a/dbm-ui/backend/db_report/report_baseview.py
+++ b/dbm-ui/backend/db_report/report_baseview.py
@@ -8,29 +8,47 @@
 an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.
 """
-from rest_framework import mixins
+from collections import defaultdict
+from typing import Dict
+
+from django.core.cache import cache
+from django.utils.translation import gettext_lazy as _
+from rest_framework import mixins, status
+from rest_framework.decorators import action
+from rest_framework.response import Response
 from rest_framework.viewsets import GenericViewSet
 
+from backend.bk_web import viewsets
 from backend.bk_web.pagination import AuditedLimitOffsetPagination
+from backend.bk_web.swagger import common_swagger_auto_schema
+from backend.configuration.models import DBAdministrator
+from backend.db_report.enums import REPORT_COUNT_CACHE_KEY, SWAGGER_TAG, ReportType
+from backend.db_report.filters import ReportFilterBackend
+from backend.db_report.register import db_report_maps
+from backend.db_report.serializers import GetReportCountSerializer, GetReportOverviewSerializer
 from backend.iam_app.dataclass import ResourceEnum
 from backend.iam_app.dataclass.actions import ActionEnum
 from backend.iam_app.handlers.drf_perm.base import ResourceActionPermission, get_request_key_id
 
 
 class ReportBaseViewSet(GenericViewSet, mixins.ListModelMixin):
+    # 分页类
     pagination_class = AuditedLimitOffsetPagination
-
+    # 巡检类型
+    report_type = None
+    # 巡检名称
+    report_name = ""
+    # 巡检表头
+    report_title = []
+    # 巡检过滤类
+    filter_backends = [ReportFilterBackend]
     filter_fields = {
         "bk_biz_id": ["exact"],
-        "cluster": ["exact", "in"],
         "cluster_type": ["exact", "in"],
         "create_at": ["gte", "lte"],
         "status": ["exact", "in"],
     }
 
-    report_name = ""
-    report_title = []
-
     @staticmethod
     def instance_getter(request, view):
         return [get_request_key_id(request, "bk_biz_id")]
@@ -42,7 +60,48 @@ def get_permissions(self):
 
     def list(self, request, *args, **kwargs):
         response = super().list(request, *args, **kwargs)
-        response.data["name"] = self.report_name
+        response.data["name"] = self.report_name or ReportType.get_choice_label(self.report_type)
         response.data["title"] = self.report_title
-
         return response
+
+
+class ReportCommonViewSet(viewsets.SystemViewSet):
+    """巡检通用接口视图"""
+
+    @common_swagger_auto_schema(
+        operation_summary=_("获取巡检报告总览"),
+        responses={status.HTTP_200_OK: GetReportOverviewSerializer()},
+        tags=[SWAGGER_TAG],
+    )
+    @action(methods=["GET"], detail=False, serializer_class=GetReportOverviewSerializer)
+    def get_report_overview(self, request, *args, **kwargs):
+        db_report_types = defaultdict(list)
+        for db_type, report_cls_list in db_report_maps.items():
+            db_report_types[db_type] = [cls.report_type for cls in report_cls_list]
+        return Response(db_report_types)
+
+    @common_swagger_auto_schema(
+        operation_summary=_("获取巡检报告代办数量"),
+        responses={status.HTTP_200_OK: GetReportCountSerializer()},
+        tags=[SWAGGER_TAG],
+    )
+    @action(methods=["GET"], detail=False, serializer_class=GetReportCountSerializer)
+    def get_report_count(self, request, *args, **kwargs):
+        username = request.user.username
+        cache_key = REPORT_COUNT_CACHE_KEY.format(user=username)
+
+        report_count_cache = cache.get(cache_key)
+        if report_count_cache:
+            return Response(report_count_cache)
+        else:
+            report_count_map: Dict[str, Dict[str, Dict]] = defaultdict(lambda: defaultdict(dict))
+            for db_type, report_classes in db_report_maps.items():
+                manage_bizs, assist_bizs = DBAdministrator.get_manage_bizs(db_type, username)
+                for cls in report_classes:
+                    report_count_map[db_type][cls.report_type].update(
+                        manage_count=cls.queryset.filter(status=False, bk_biz_id__in=manage_bizs).count(),
+                        assist_count=cls.queryset.filter(status=False, bk_biz_id__in=assist_bizs).count(),
+                    )
+            # 数量精确性不高,可以做1h的缓存
+            cache.set(cache_key, report_count_map, 60 * 60)
+            return Response(report_count_map)
diff --git a/dbm-ui/backend/db_report/serializers/meta_check_report_serializer.py b/dbm-ui/backend/db_report/serializers.py
similarity index 71%
rename from dbm-ui/backend/db_report/serializers/meta_check_report_serializer.py
rename to dbm-ui/backend/db_report/serializers.py
index 37e1436a30..467807fb7b 100644
--- a/dbm-ui/backend/db_report/serializers/meta_check_report_serializer.py
+++ b/dbm-ui/backend/db_report/serializers.py
@@ -10,10 +10,14 @@
 """
 from rest_framework import serializers
 
-from backend.db_report.models import MetaCheckReport
+from backend.db_report import mock_data
 
 
-class MetaCheckReportSerializer(serializers.ModelSerializer):
+class GetReportOverviewSerializer(serializers.Serializer):
     class Meta:
-        model = MetaCheckReport
-        fields = "__all__"
+        swagger_schema_fields = {"example": mock_data.REPORT_OVERVIEW_DATA}
+
+
+class GetReportCountSerializer(serializers.Serializer):
+    class Meta:
+        swagger_schema_fields = {"example": mock_data.REPORT_COUNT_DATA}
diff --git a/dbm-ui/backend/db_report/urls.py b/dbm-ui/backend/db_report/urls.py
index 18d0081fa1..f1b908c0a1 100644
--- a/dbm-ui/backend/db_report/urls.py
+++ b/dbm-ui/backend/db_report/urls.py
@@ -8,19 +8,20 @@
 an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.
 """
-from django.conf.urls import url
+from rest_framework.routers import DefaultRouter
 
-from backend.db_report import views
+from backend.db_report.register import db_report_maps
+from backend.db_report.report_baseview import ReportCommonViewSet
+from backend.db_report.views.checksum_instance_view import ChecksumInstanceViewSet
 
-urlpatterns = [
-    url("^meta_check/instance_belong$", views.MetaCheckReportInstanceBelongViewSet.as_view({"get": "list"})),
-    url("^checksum_check/report$", views.ChecksumCheckReportViewSet.as_view({"get": "list"})),
-    url("^checksum_check/instance$", views.ChecksumInstanceViewSet.as_view({"get": "list"})),
-    url("^mysql_check/full_backup$", views.MysqlFullBackupCheckReportViewSet.as_view({"get": "list"})),
-    url("^mysql_check/binlog_backup$", views.MysqlBinlogBackupCheckReportViewSet.as_view({"get": "list"})),
-    url("^redis_check/full_backup$", views.RedisFullBackupCheckReportViewSet.as_view({"get": "list"})),
-    url("^redis_check/binlog_backup$", views.RedisBinlogBackupCheckReportViewSet.as_view({"get": "list"})),
-    url("^dbmon/heartbeat$", views.DbmonHeatbeartCheckReportBaseViewSet.as_view({"get": "list"})),
-    url("^redis_meta_check/status_abnormal$", views.RedisStatusAbnormalCheckReportViewSet.as_view({"get": "list"})),
-    url("^redis_meta_check/alone_instance$", views.RedisAloneInstanceCheckReportViewSet.as_view({"get": "list"})),
-]
+routers = DefaultRouter(trailing_slash=True)
+
+routers.register(r"", ReportCommonViewSet, basename="report_common")
+routers.register(r"checksum_instance", ChecksumInstanceViewSet, basename="checksum_instance")
+
+# 自动添加注册的巡检视图
+for db_type, reports in db_report_maps.items():
+    for report in reports:
+        routers.register(f"{db_type}/{report.report_type}", report, basename=f"{db_type}-{report.report_type}")
+
+urlpatterns = routers.urls
diff --git a/dbm-ui/backend/db_report/views/__init__.py b/dbm-ui/backend/db_report/views/__init__.py
index 2bf74b43f2..775d4ec623 100644
--- a/dbm-ui/backend/db_report/views/__init__.py
+++ b/dbm-ui/backend/db_report/views/__init__.py
@@ -8,10 +8,5 @@
 an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.
 """
-from .checksum_check_report_view import ChecksumCheckReportViewSet
-from .checksum_instance_view import ChecksumInstanceViewSet
-from .dbmon_heartbeat_view import DbmonHeatbeartCheckReportBaseViewSet
-from .meta_check_view import MetaCheckReportInstanceBelongViewSet
-from .mysqlbackup_check_view import MysqlBinlogBackupCheckReportViewSet, MysqlFullBackupCheckReportViewSet
-from .redis_dbmeta_check_view import RedisAloneInstanceCheckReportViewSet, RedisStatusAbnormalCheckReportViewSet
-from .redisbackup_check_view import RedisBinlogBackupCheckReportViewSet, RedisFullBackupCheckReportViewSet
+from .mysql import *
+from .redis import *
diff --git a/dbm-ui/backend/db_report/views/checksum_check_report_view.py b/dbm-ui/backend/db_report/views/checksum_check_report_view.py
index 5a24b95e3a..79905f5f77 100644
--- a/dbm-ui/backend/db_report/views/checksum_check_report_view.py
+++ b/dbm-ui/backend/db_report/views/checksum_check_report_view.py
@@ -12,7 +12,7 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
@@ -31,16 +31,9 @@ class Meta:
         swagger_schema_fields = {"example": mock_data.CHECKSUM_CHECK_DATA}
 
 
-class ChecksumCheckReportViewSet(ReportBaseViewSet):
+class ChecksumCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = ChecksumCheckReport.objects.all().order_by("-create_at")
     serializer_class = ChecksumCheckReportSerializer
-    filter_fields = {
-        "bk_biz_id": ["exact"],
-        "cluster_type": ["exact"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-        "cluster": ["exact", "in"],
-    }
     report_name = _("数据校验")
     report_title = [
         {
diff --git a/dbm-ui/backend/db_report/views/checksum_instance_view.py b/dbm-ui/backend/db_report/views/checksum_instance_view.py
index 744943bb57..e9447ea3af 100644
--- a/dbm-ui/backend/db_report/views/checksum_instance_view.py
+++ b/dbm-ui/backend/db_report/views/checksum_instance_view.py
@@ -12,7 +12,8 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
+from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
@@ -32,6 +33,7 @@ class Meta:
 class ChecksumInstanceViewSet(ReportBaseViewSet):
     queryset = ChecksumInstance.objects.all()
     serializer_class = ChecksumInstanceSerializer
+    filter_backends = [DjangoFilterBackend]
     filter_fields = {
         "report_id": ["exact"],
     }
diff --git a/dbm-ui/backend/db_report/views/meta_check_view.py b/dbm-ui/backend/db_report/views/meta_check_view.py
index 5ed61348b9..c1da0c0e6e 100644
--- a/dbm-ui/backend/db_report/views/meta_check_view.py
+++ b/dbm-ui/backend/db_report/views/meta_check_view.py
@@ -12,7 +12,7 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
@@ -24,23 +24,17 @@
 logger = logging.getLogger("root")
 
 
-class MetaCheckReportInstanceBelongSerializer(serializers.ModelSerializer):
+class MetaCheckReportSerializer(serializers.ModelSerializer):
     class Meta:
         model = MetaCheckReport
         fields = ("bk_biz_id", "ip", "port", "machine_type", "status", "msg", "create_at")
         swagger_schema_fields = {"example": mock_data.META_CHECK_DATA}
 
 
-class MetaCheckReportInstanceBelongViewSet(ReportBaseViewSet):
+class MetaCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = MetaCheckReport.objects.all()
-    serializer_class = MetaCheckReportInstanceBelongSerializer
-    filter_fields = {  # 大部分时候不需要覆盖默认的filter
-        "bk_biz_id": ["exact"],
-        "cluster_type": ["exact", "in"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-    }
-    report_name = _("实例集群归属")
+    serializer_class = MetaCheckReportSerializer
+    report_name = _("元数据检查")
     report_title = [
         {
             "name": "bk_biz_id",
@@ -81,7 +75,7 @@ class MetaCheckReportInstanceBelongViewSet(ReportBaseViewSet):
 
     @common_swagger_auto_schema(
         operation_summary=_("元数据检查报告列表"),
-        responses={status.HTTP_200_OK: MetaCheckReportInstanceBelongSerializer()},
+        responses={status.HTTP_200_OK: MetaCheckReportSerializer()},
         tags=[SWAGGER_TAG],
     )
     def list(self, request, *args, **kwargs):
diff --git a/dbm-ui/backend/db_report/views/mysql/mysql_checksum_report_view.py b/dbm-ui/backend/db_report/views/mysql/mysql_checksum_report_view.py
new file mode 100644
index 0000000000..2c7407293a
--- /dev/null
+++ b/dbm-ui/backend/db_report/views/mysql/mysql_checksum_report_view.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+"""
+tencentblueking is pleased to support the open source community by making 蓝鲸智云-db管理系统(blueking-bk-dbm) available.
+copyright (c) 2017-2023 thl a29 limited, a tencent company. all rights reserved.
+licensed under the mit license (the "license"); you may not use this file except in compliance with the license.
+you may obtain a copy of the license at https://opensource.org/licenses/mit
+unless required by applicable law or agreed to in writing, software distributed under the license is distributed on
+an "as is" basis, without warranties or conditions of any kind, either express or implied. see the license for the
+specific language governing permissions and limitations under the license.
+"""
+from backend.configuration.constants import DBType
+from backend.db_meta.enums import ClusterType
+from backend.db_report.enums import ReportType
+from backend.db_report.models import ChecksumCheckReport
+from backend.db_report.register import register_report
+from backend.db_report.views.checksum_check_report_view import ChecksumCheckReportBaseViewSet
+
+
+@register_report(DBType.MySQL)
+class MySQLChecksumCheckReportViewSet(ChecksumCheckReportBaseViewSet):
+    report_type = ReportType.CHECKSUM
+    cluster_types = ClusterType.db_type_to_cluster_types(DBType.MySQL)
+    queryset = ChecksumCheckReport.objects.filter(cluster_type__in=cluster_types).order_by("-create_at")
diff --git a/dbm-ui/backend/db_report/serializers/__init__.py b/dbm-ui/backend/db_report/views/mysql/mysql_dbmeta_check_view.py
similarity index 53%
rename from dbm-ui/backend/db_report/serializers/__init__.py
rename to dbm-ui/backend/db_report/views/mysql/mysql_dbmeta_check_view.py
index aa5085c628..fb6a22cc4a 100644
--- a/dbm-ui/backend/db_report/serializers/__init__.py
+++ b/dbm-ui/backend/db_report/views/mysql/mysql_dbmeta_check_view.py
@@ -8,3 +8,17 @@
 an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 specific language governing permissions and limitations under the License.
 """
+
+from backend.configuration.constants import DBType
+from backend.db_meta.enums import ClusterType
+from backend.db_report.enums import ReportType
+from backend.db_report.models import MetaCheckReport
+from backend.db_report.register import register_report
+from backend.db_report.views.meta_check_view import MetaCheckReportBaseViewSet
+
+
+@register_report(DBType.MySQL)
+class MySQLMetaCheckReportViewSet(MetaCheckReportBaseViewSet):
+    report_type = ReportType.META_CHECK
+    cluster_types = ClusterType.db_type_to_cluster_types(DBType.MySQL)
+    queryset = MetaCheckReport.objects.filter(cluster_type__in=cluster_types).order_by("-create_at")
diff --git a/dbm-ui/backend/db_report/views/mysqlbackup_check_view.py b/dbm-ui/backend/db_report/views/mysql/mysqlbackup_check_view.py
similarity index 89%
rename from dbm-ui/backend/db_report/views/mysqlbackup_check_view.py
rename to dbm-ui/backend/db_report/views/mysql/mysqlbackup_check_view.py
index 5eb75d4c06..9503cdf96e 100644
--- a/dbm-ui/backend/db_report/views/mysqlbackup_check_view.py
+++ b/dbm-ui/backend/db_report/views/mysql/mysqlbackup_check_view.py
@@ -12,13 +12,15 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
+from backend.configuration.constants import DBType
 from backend.db_report import mock_data
-from backend.db_report.enums import SWAGGER_TAG, MysqlBackupCheckSubType, ReportFieldFormat
+from backend.db_report.enums import SWAGGER_TAG, MysqlBackupCheckSubType, ReportFieldFormat, ReportType
 from backend.db_report.models import MysqlBackupCheckReport
+from backend.db_report.register import register_report
 from backend.db_report.report_baseview import ReportBaseViewSet
 
 logger = logging.getLogger("root")
@@ -34,14 +36,6 @@ class Meta:
 class MysqlBackupCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = MysqlBackupCheckReport.objects.all()
     serializer_class = MysqlBackupCheckReportSerializer
-    filter_fields = {  # 大部分时候不需要覆盖默认的filter
-        "bk_biz_id": ["exact"],
-        "cluster": ["exact", "in"],
-        "cluster_type": ["exact", "in"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-    }
-    report_name = _("集群全备检查")
     report_title = [
         {
             "name": "bk_biz_id",
@@ -85,10 +79,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.MySQL)
 class MysqlFullBackupCheckReportViewSet(MysqlBackupCheckReportBaseViewSet):
     queryset = MysqlBackupCheckReport.objects.filter(subtype=MysqlBackupCheckSubType.FullBackup.value)
     serializer_class = MysqlBackupCheckReportSerializer
-    report_name = _("MySQL 全备检查")
+    report_type = ReportType.FULL_BACKUP_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("MySQL 全备检查报告"),
@@ -99,10 +94,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.MySQL)
 class MysqlBinlogBackupCheckReportViewSet(MysqlBackupCheckReportBaseViewSet):
     queryset = MysqlBackupCheckReport.objects.filter(subtype=MysqlBackupCheckSubType.BinlogSeq.value)
     serializer_class = MysqlBackupCheckReportSerializer
-    report_name = _("集群binlog检查")
+    report_type = ReportType.BINLOG_BACKUP_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("MySQL binlog检查报告"),
diff --git a/dbm-ui/backend/db_report/views/dbmon_heartbeat_view.py b/dbm-ui/backend/db_report/views/redis/dbmon_heartbeat_view.py
similarity index 89%
rename from dbm-ui/backend/db_report/views/dbmon_heartbeat_view.py
rename to dbm-ui/backend/db_report/views/redis/dbmon_heartbeat_view.py
index 736c70327a..57c56c88d5 100644
--- a/dbm-ui/backend/db_report/views/dbmon_heartbeat_view.py
+++ b/dbm-ui/backend/db_report/views/redis/dbmon_heartbeat_view.py
@@ -12,13 +12,15 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
+from backend.configuration.constants import DBType
 from backend.db_report import mock_data
-from backend.db_report.enums import SWAGGER_TAG, ReportFieldFormat
+from backend.db_report.enums import SWAGGER_TAG, ReportFieldFormat, ReportType
 from backend.db_report.models import DbmonHeartbeatReport
+from backend.db_report.register import register_report
 from backend.db_report.report_baseview import ReportBaseViewSet
 
 logger = logging.getLogger("root")
@@ -31,17 +33,11 @@ class Meta:
         swagger_schema_fields = {"example": mock_data.DBMON_HEARTBEAT_CHECK_DATA}
 
 
+@register_report(DBType.Redis)
 class DbmonHeatbeartCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = DbmonHeartbeatReport.objects.all()
     serializer_class = DbmonHeartbeatCheckReportSerializer
-    filter_fields = {  # 大部分时候不需要覆盖默认的filter
-        "bk_biz_id": ["exact"],
-        "cluster_type": ["exact", "in"],
-        "cluster": ["exact", "in"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-    }
-    report_name = _("dbmon心跳超时检查")
+    report_type = ReportType.REDIS_DBMON_HEARTBEAT_CHECK
     report_title = [
         {
             "name": "bk_biz_id",
diff --git a/dbm-ui/backend/db_report/views/redis_dbmeta_check_view.py b/dbm-ui/backend/db_report/views/redis/redis_dbmeta_check_view.py
similarity index 89%
rename from dbm-ui/backend/db_report/views/redis_dbmeta_check_view.py
rename to dbm-ui/backend/db_report/views/redis/redis_dbmeta_check_view.py
index 1c973c06fa..ea751bd43c 100644
--- a/dbm-ui/backend/db_report/views/redis_dbmeta_check_view.py
+++ b/dbm-ui/backend/db_report/views/redis/redis_dbmeta_check_view.py
@@ -11,13 +11,15 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
+from backend.configuration.constants import DBType
 from backend.db_report import mock_data
-from backend.db_report.enums import SWAGGER_TAG, MetaCheckSubType, ReportFieldFormat
+from backend.db_report.enums import SWAGGER_TAG, MetaCheckSubType, ReportFieldFormat, ReportType
 from backend.db_report.models import MetaCheckReport
+from backend.db_report.register import register_report
 from backend.db_report.report_baseview import ReportBaseViewSet
 
 logger = logging.getLogger("root")
@@ -33,14 +35,6 @@ class Meta:
 class RedisDbmetaCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = MetaCheckReport.objects.all()
     serializer_class = RedisDbmetaCheckReportSerializer
-    filter_fields = {  # 大部分时候不需要覆盖默认的filter
-        "bk_biz_id": ["exact"],
-        "cluster": ["exact", "in"],
-        "cluster_type": ["exact", "in"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-    }
-    report_name = _("redis 元数据检查")
     report_title = [
         {
             "name": "bk_biz_id",
@@ -84,10 +78,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.Redis)
 class RedisAloneInstanceCheckReportViewSet(RedisDbmetaCheckReportBaseViewSet):
     queryset = MetaCheckReport.objects.filter(subtype=MetaCheckSubType.AloneInstance.value)
     serializer_class = RedisDbmetaCheckReportSerializer
-    report_name = _("孤立节点检查")
+    report_type = ReportType.ALONE_INSTANCE_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("孤立节点检查报告"),
@@ -98,10 +93,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.Redis)
 class RedisStatusAbnormalCheckReportViewSet(RedisDbmetaCheckReportBaseViewSet):
     queryset = MetaCheckReport.objects.filter(subtype=MetaCheckSubType.StatusAbnormal.value)
     serializer_class = RedisDbmetaCheckReportSerializer
-    report_name = _("实例状态异常检查")
+    report_type = ReportType.STATUS_ABNORMAL_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("实例状态异常检查"),
diff --git a/dbm-ui/backend/db_report/views/redisbackup_check_view.py b/dbm-ui/backend/db_report/views/redis/redisbackup_check_view.py
similarity index 89%
rename from dbm-ui/backend/db_report/views/redisbackup_check_view.py
rename to dbm-ui/backend/db_report/views/redis/redisbackup_check_view.py
index 52caecf5f8..c05eb433af 100644
--- a/dbm-ui/backend/db_report/views/redisbackup_check_view.py
+++ b/dbm-ui/backend/db_report/views/redis/redisbackup_check_view.py
@@ -11,13 +11,15 @@
 
 import logging
 
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext as _
 from rest_framework import serializers, status
 
 from backend.bk_web.swagger import common_swagger_auto_schema
+from backend.configuration.constants import DBType
 from backend.db_report import mock_data
-from backend.db_report.enums import SWAGGER_TAG, RedisBackupCheckSubType, ReportFieldFormat
+from backend.db_report.enums import SWAGGER_TAG, RedisBackupCheckSubType, ReportFieldFormat, ReportType
 from backend.db_report.models import RedisBackupCheckReport
+from backend.db_report.register import register_report
 from backend.db_report.report_baseview import ReportBaseViewSet
 
 logger = logging.getLogger("root")
@@ -33,14 +35,6 @@ class Meta:
 class RedisBackupCheckReportBaseViewSet(ReportBaseViewSet):
     queryset = RedisBackupCheckReport.objects.all()
     serializer_class = RedisBackupCheckReportSerializer
-    filter_fields = {  # 大部分时候不需要覆盖默认的filter
-        "bk_biz_id": ["exact"],
-        "cluster": ["exact", "in"],
-        "cluster_type": ["exact", "in"],
-        "create_at": ["gte", "lte"],
-        "status": ["exact", "in"],
-    }
-    report_name = _("集群备份检查")
     report_title = [
         {
             "name": "bk_biz_id",
@@ -89,10 +83,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.Redis)
 class RedisFullBackupCheckReportViewSet(RedisBackupCheckReportBaseViewSet):
     queryset = RedisBackupCheckReport.objects.filter(subtype=RedisBackupCheckSubType.FullBackup.value)
     serializer_class = RedisBackupCheckReportSerializer
-    report_name = _("Redis 全备检查")
+    report_type = ReportType.FULL_BACKUP_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("Redis 全备检查报告"),
@@ -103,11 +98,11 @@ def list(self, request, *args, **kwargs):
         return super().list(request, *args, **kwargs)
 
 
+@register_report(DBType.Redis)
 class RedisBinlogBackupCheckReportViewSet(RedisBackupCheckReportBaseViewSet):
     queryset = RedisBackupCheckReport.objects.filter(subtype=RedisBackupCheckSubType.BinlogBackup.value)
-
     serializer_class = RedisBackupCheckReportSerializer
-    report_name = _("Redis集群binlog检查")
+    report_type = ReportType.BINLOG_BACKUP_CHECK
 
     @common_swagger_auto_schema(
         operation_summary=_("Redis binlog检查报告"),
diff --git a/dbm-ui/backend/ticket/builders/__init__.py b/dbm-ui/backend/ticket/builders/__init__.py
index 77828cd731..7dce423d3b 100644
--- a/dbm-ui/backend/ticket/builders/__init__.py
+++ b/dbm-ui/backend/ticket/builders/__init__.py
@@ -26,6 +26,7 @@
 from backend.iam_app.dataclass.actions import ActionEnum
 from backend.ticket.constants import TICKET_EXPIRE_DEFAULT_CONFIG, FlowRetryType, FlowType, TicketType
 from backend.ticket.models import Flow, Ticket, TicketFlowsConfig
+from backend.utils.register import re_import_modules
 
 logger = logging.getLogger("root")
 
@@ -515,19 +516,5 @@ def create_builder(cls, ticket: Ticket):
         return builder_cls(ticket)
 
 
-def register_all_builders(path=os.path.dirname(__file__), module_path="backend.ticket.builders"):
-    """递归注册当前目录下所有的构建器"""
-    for name in os.listdir(path):
-        # 忽略无效文件
-        if name.endswith(".pyc") or name in ["__init__.py", "__pycache__"]:
-            continue
-
-        if os.path.isdir(os.path.join(path, name)):
-            register_all_builders(os.path.join(path, name), ".".join([module_path, name]))
-        else:
-            try:
-                module_name = name.replace(".py", "")
-                import_path = ".".join([module_path, module_name])
-                importlib.import_module(import_path)
-            except ModuleNotFoundError as e:
-                logger.warning(e)
+def register_all_builders():
+    re_import_modules(path=os.path.dirname(__file__), module_path="backend.ticket.builders")
diff --git a/dbm-ui/backend/ticket/todos/__init__.py b/dbm-ui/backend/ticket/todos/__init__.py
index 6158196fe4..45d22270d6 100644
--- a/dbm-ui/backend/ticket/todos/__init__.py
+++ b/dbm-ui/backend/ticket/todos/__init__.py
@@ -21,6 +21,7 @@
 from backend.ticket.constants import TODO_RUNNING_STATUS
 from backend.ticket.exceptions import TodoDuplicateProcessException, TodoWrongOperatorException
 from backend.ticket.models import Todo
+from backend.utils.register import re_import_modules
 from blue_krill.data_types.enum import EnumField, StructuredEnum
 
 logger = logging.getLogger("root")
@@ -106,22 +107,8 @@ def actor(cls, todo: Todo) -> TodoActor:
         return todo_cls(todo)
 
 
-def register_all_todos(path=os.path.dirname(__file__), module_path="backend.ticket.todos"):
-    """递归注册当前目录下所有的todo处理器"""
-    for name in os.listdir(path):
-        # 忽略无效文件
-        if name.endswith(".pyc") or name in ["__init__.py", "__pycache__"]:
-            continue
-
-        if os.path.isdir(os.path.join(path, name)):
-            register_all_todos(os.path.join(path, name), ".".join([module_path, name]))
-        else:
-            try:
-                module_name = name.replace(".py", "")
-                import_path = ".".join([module_path, module_name])
-                importlib.import_module(import_path)
-            except ModuleNotFoundError as e:
-                logger.warning(e)
+def register_all_todos():
+    re_import_modules(path=os.path.dirname(__file__), module_path="backend.ticket.todos")
 
 
 class ActionType(str, StructuredEnum):
diff --git a/dbm-ui/backend/urls.py b/dbm-ui/backend/urls.py
index 51f2d65164..62d973527d 100644
--- a/dbm-ui/backend/urls.py
+++ b/dbm-ui/backend/urls.py
@@ -75,6 +75,7 @@
     path("grafana/", include("backend.bk_dataview.grafana.urls")),
     # 版本日志
     path("version_log/", include("backend.version_log.urls")),
+    # 巡检报告
     path("db_report/", include("backend.db_report.urls")),
     # 接入消息通知
     path("{}".format(config.ENTRANCE_URL), include("bk_notice_sdk.urls")),
diff --git a/dbm-ui/backend/utils/register.py b/dbm-ui/backend/utils/register.py
new file mode 100644
index 0000000000..6a9e194f25
--- /dev/null
+++ b/dbm-ui/backend/utils/register.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
+Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
+Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at https://opensource.org/licenses/MIT
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+"""
+import importlib
+import logging
+import os
+
+logger = logging.getLogger("root")
+
+
+def re_import_modules(path, module_path):
+    """递归导入文件下的模块,通常用于触发注册器逻辑"""
+    for name in os.listdir(path):
+        # 忽略无效文件
+        if name.endswith(".pyc") or name in ["__init__.py", "__pycache__"]:
+            continue
+
+        if os.path.isdir(os.path.join(path, name)):
+            re_import_modules(os.path.join(path, name), ".".join([module_path, name]))
+        else:
+            try:
+                module_name = name.replace(".py", "")
+                import_path = ".".join([module_path, module_name])
+                importlib.import_module(import_path)
+            except ModuleNotFoundError as e:
+                logger.warning(e)