Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions dbm-ui/backend/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from backend.components.domains import ESB_PREFIX
from backend.components.exception import DataAPIException
from backend.configuration.models.system import SystemSettings
from backend.exceptions import ApiError, ApiRequestError, ApiResultError, AppBaseException
from backend.exceptions import ApiError, ApiRequestError, ApiResultError, AppBaseException, ValidationError
from backend.utils.local import local

logger = logging.getLogger("root")
Expand Down Expand Up @@ -413,6 +413,39 @@ def _set_cache(self, cache_key, data):
"""
cache.set(cache_key, data, self.cache_time)

def _get_cached_admin_username(self):
"""
获取缓存的租户管理员用户名
"""
if not env.ENABLE_MULTI_TENANT_MODE:
return env.DEFAULT_USERNAME

login_name = "bk_admin"
cache_key = f"dbm:tenant_admin_username:{env.BK_TENANT_ID}:{login_name}"
bk_username = cache.get(cache_key, "")
if not bk_username:
try:
from backend.components.usermanage.client import UserManagerApi

params = {"lookup_field": "login_name", "lookups": login_name}
# 接口返回不规范(无result/code字段),使用raw=True绕过DataAPI的统一校验
res = UserManagerApi.batch_lookup_virtual_user(params, use_admin=False, use_param_user=True, raw=True)
data = res.get("data")
if isinstance(data, list) and data:
bk_username = data[0].get("bk_username") or data[0].get("username")
elif isinstance(data, dict) and data:
bk_username = data.get("bk_username") or data.get("username")
if bk_username:
cache.set(cache_key, bk_username, 60 * 60 * 24)
else:
raise ValidationError(_("获取租户管理员账号失败: 未能从响应中获取用户名"))
except Exception as e:
error_msg = _("获取租户管理员账号失败: {error}").format(error=str(e))
logger.error(error_msg)
raise ValidationError(error_msg)

return bk_username

def _set_session_headers(self, session, local_request, headers: Dict, params: Dict, use_admin: bool = False):
"""
设置session的headers
Expand All @@ -430,17 +463,23 @@ def _set_session_headers(self, session, local_request, headers: Dict, params: Di
# 增加鉴权信息
if not isinstance(params, dict):
return

bk_token = session.headers.get("bk_token", "")
if not bk_token and local_request and hasattr(local_request, "COOKIES"):
bk_token = local_request.COOKIES.get("bk_token", "")

bkapi_auth_headers = {
"bk_app_code": params.pop("bk_app_code", env.APP_CODE),
"bk_app_secret": params.pop("bk_app_secret", env.SECRET_KEY),
"bk_username": params.get("bk_username", "Anonymous"),
"bk_token": bk_token,
}
if use_admin:
# 使用管理员/平台身份调用接口
bkapi_auth_headers["bk_username"] = env.DEFAULT_USERNAME
bkapi_auth_headers["bk_username"] = self._get_cached_admin_username()
elif self.is_backend_request(local_request) and not self.use_param_user:
# 后台调用(且不明确用户),使用管理员/平台身份调用接口
bkapi_auth_headers["bk_username"] = env.DEFAULT_USERNAME
bkapi_auth_headers["bk_username"] = self._get_cached_admin_username()
elif local_request and local_request.COOKIES:
# 根据不同环境,传递认证信息
bkapi_auth_headers["bk_username"] = local_request.user.username
Expand Down
52 changes: 46 additions & 6 deletions dbm-ui/backend/components/cmsi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,57 @@ class _CmsiApi(BaseApi):
MODULE = _("消息管理")
BASE = CMSI_APIGW_DOMAIN

# 消息类型映射定义
@property
def MSG_TYPE_MAP(self):
from ...core.notify.constants import MsgType

return {
MsgType.VOICE.value: "send_voice",
MsgType.SMS.value: "send_sms",
MsgType.WEIXIN.value: "send_weixin",
MsgType.MAIL.value: "send_mail",
}

def __init__(self):
self.send_msg = self.generate_data_api(
method="POST",
url="send_msg/",
description=_("通用消息发送"),
)
self.get_msg_type = self.generate_data_api(
method="GET",
url="get_msg_type/",
url="channels/",
description=_("查询通知类型"),
)
self.send_voice = self.generate_data_api(
method="POST",
url="send_voice/",
description=_("语音通知"),
)
self.send_sms = self.generate_data_api(
method="POST",
url="send_sms/",
description=_("短信通知"),
)
self.send_weixin = self.generate_data_api(
method="POST",
url="send_weixin/",
description=_("微信通知"),
)
self.send_mail = self.generate_data_api(
method="POST",
url="send_mail/",
description=_("邮件通知"),
)

def get_msg_map(self):
return {msg_type: getattr(self, method_name) for msg_type, method_name in self.MSG_TYPE_MAP.items()}

def send_msg(self, params):
msg_type = params.pop("msg_type")
if not msg_type:
raise (_("消息类型(msg_type)不能为空"))

msg_map = self.get_msg_map()
if msg_type not in msg_map:
raise (_("不支持的消息类型: {}").format(msg_type))
return msg_map[msg_type](params)


CmsiApi = _CmsiApi()
3 changes: 2 additions & 1 deletion dbm-ui/backend/components/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@

from backend import env

# esb
ESB_PREFIX = "/api/c/compapi/v2/"

ESB_DOMAIN_TPL = "{}{}{{}}/".format(env.BK_COMPONENT_API_URL, ESB_PREFIX)


# 优先取环境变量的配置,若未配置对应的环境变量,则取paas默认的esb地址
CC_APIGW_DOMAIN = env.CC_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("cc")
GSE_APIGW_DOMAIN = env.GSE_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("gse")
Expand Down
10 changes: 8 additions & 2 deletions dbm-ui/backend/components/usermanage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,23 @@ class _UserManageApi(BaseApi):
BASE = USER_MANAGE_APIGW_DOMAIN

def __init__(self):
is_esb = self.is_esb()
self.list_users = self.generate_data_api(
method="GET",
url="list_users/",
url="list_users/" if is_esb else "tenant/users/",
description=_("获取所有用户"),
cache_time=300,
)
self.retrieve_user = self.generate_data_api(
method="GET",
url="retrieve_user/",
url="retrieve_user/" if is_esb else "tenant/users/{bk_username}/",
description=_("获取单个用户"),
)
self.batch_lookup_virtual_user = self.generate_data_api(
method="GET",
url="tenant/virtual-users/-/lookup/",
description=_("获取租户的管理员用户"),
)


UserManagerApi = _UserManageApi()
2 changes: 2 additions & 0 deletions dbm-ui/backend/configuration/views/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from rest_framework.decorators import action
from rest_framework.response import Response

from backend import env
from backend.bk_web import viewsets
from backend.bk_web.swagger import common_swagger_auto_schema
from backend.configuration.constants import ProfileLabel
Expand Down Expand Up @@ -48,6 +49,7 @@ def get_profile(self, request, *args, **kwargs):
"profile": list(profile),
"is_superuser": request.user.is_superuser,
"is_dba": DBAdministrator.is_dba(request.user.username),
"tenant_id": env.BK_TENANT_ID,
}
)

Expand Down
7 changes: 4 additions & 3 deletions dbm-ui/backend/configuration/views/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ def environ(self, request):
if not env.ENABLE_EXTERNAL_PROXY and not env.ENABLE_OPEN_EXTERNAL_PROXY:
envs.update(
{
"CC_IDLE_MODULE_ID": CcManage(env.DBA_APP_BK_BIZ_ID, "").get_biz_internal_module(
env.DBA_APP_BK_BIZ_ID
)[IDLE_HOST_MODULE]["bk_module_id"],
"CC_IDLE_MODULE_ID": CcManage(env.DBA_APP_BK_BIZ_ID, "")
.get_biz_internal_module(env.DBA_APP_BK_BIZ_ID)
.get(IDLE_HOST_MODULE, {})
.get("bk_module_id"),
"BK_COMPONENT_API_URL": env.BK_COMPONENT_API_URL,
"BK_CMDB_URL": env.BK_CMDB_URL,
"BK_NODEMAN_URL": env.BK_NODEMAN_URL,
Expand Down
72 changes: 61 additions & 11 deletions dbm-ui/backend/core/notify/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,72 @@ class CmsiHandler(BaseNotifyHandler):
def get_msg_type(cls):
return [s["type"] for s in CmsiApi.get_msg_type()]

def _build_mail_params(self, **kwargs):
"""构建邮件参数"""
params = {
"title": self.title,
"content": self.content.replace("\n", "<br>"), # 邮件换行用<br>
"is_content_base64": False,
}
params.update(kwargs) # 更新额外参数(sender, cc__username等)
return params

def _build_sms_params(self, **kwargs):
"""构建短信参数"""
return {
"content": f"{self.title}\n{self.content}", # 短信合并标题和内容
"is_content_base64": False,
}

def _build_voice_params(self, **kwargs):
"""构建语音参数"""
return {
"auto_read_message": f"{self.title}\n{self.content}", # 语音播报内容
}

def _build_weixin_params(self, **kwargs):
"""构建微信参数"""
return {
"message_data": {
"heading": self.title,
"message": self.content,
"is_message_base64": False,
}
}

def _build_default_params(self, **kwargs):
"""构建默认参数(保留 RTX、企微机器人等功能的参数)"""
params = {
"title": self.title,
"content": self.content,
}
params.update(kwargs)
return params

def _cmsi_send_msg(self, msg_type: str, **kwargs):
"""
统一处理所有消息类型的发送逻辑
@param msg_type: 发送类型
@param kwargs: 额外参数
"""
msg_info = {
"msg_type": msg_type,
"receiver__username": ",".join(self.receivers),
"title": self.title,
"content": self.content,
"receiver__username": self.receivers, # 接收者列表
}
msg_info.update(kwargs)

# 策略映射:根据消息类型选择对应的参数构建方法
param_builders_map = {
MsgType.MAIL.value: self._build_mail_params,
MsgType.SMS.value: self._build_sms_params,
MsgType.VOICE.value: self._build_voice_params,
MsgType.WEIXIN.value: self._build_weixin_params,
}

# 获取对应的参数构建器,如果没有则使用默认构建器
builder = param_builders_map.get(msg_type, self._build_default_params)
msg_info.update(builder(**kwargs))

# 调用统一的send_msg接口
CmsiApi.send_msg(msg_info)

def send_mail(self, sender: str = None, cc: list = None):
Expand All @@ -201,12 +255,10 @@ def send_mail(self, sender: str = None, cc: list = None):
"""
kwargs = {}
if sender:
kwargs.update(sender=sender)
kwargs["sender"] = sender
if cc:
kwargs.update(cc__username=",".join(cc))
# 邮件的换行要用<br>的html
self.content = self.content.replace("\n", "<br>")
self._cmsi_send_msg(MsgType.MAIL, **kwargs)
kwargs["cc__username"] = cc
self._cmsi_send_msg(MsgType.MAIL.value, **kwargs)

def send_voice(self):
"""发送语音消息"""
Expand All @@ -222,8 +274,6 @@ def send_rtx(self):

def send_sms(self):
"""发送短信消息"""
# 短信消息没有标题参数,直接把标题和内容放在一起
self.content = f"{self.title}\n{self.content}"
self._cmsi_send_msg(MsgType.SMS.value)

def send_wecom_robot(self):
Expand Down
3 changes: 3 additions & 0 deletions dbm-ui/backend/flow/utils/cc_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ def get_or_create_set_module(
def get_biz_internal_module(bk_biz_id: int):
"""获取业务下的内置模块"""
biz_internal_module = ResourceQueryHelper.get_biz_internal_module(bk_biz_id)
if not biz_internal_module or not biz_internal_module.get("module"):
logger.warning(f"biz_internal_module is empty, bk_biz_id: {bk_biz_id}")
return {}
module_type__module = {module["default"]: module for module in biz_internal_module["module"]}
return module_type__module

Expand Down
2 changes: 1 addition & 1 deletion dbm-ui/backend/iam_app/handlers/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, username: str = "", request=None):
@classmethod
def get_iam_client(cls):
tenant_id = getattr(env, "BK_TENANT_ID", "")
if settings.BK_IAM_SKIP:
if env.BK_IAM_SKIP:
return DummyIAM(env.APP_CODE, env.SECRET_KEY, env.BK_IAM_APIGATEWAY, tenant_id)
return IAM(env.APP_CODE, env.SECRET_KEY, env.BK_IAM_APIGATEWAY, tenant_id)

Expand Down
8 changes: 7 additions & 1 deletion dbm-ui/backend/tests/components/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ def test_call_raise_exception_on_failure(self, mock_session):
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.json.return_value = {"result": False, "message": "error", "code": 1}
mock_session.return_value.request.return_value = mock_resp

# 设置完整的mock session
mock_session_instance = MagicMock()
mock_session_instance.request.return_value = mock_resp
mock_session_instance.headers = {}
mock_session_instance.cookies = {}
mock_session.return_value = mock_session_instance

api = DataAPI(method="GET", base="http://test.com", url="/api/test", module="test")
with pytest.raises(ApiResultError):
Expand Down
8 changes: 4 additions & 4 deletions dbm-ui/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dbm-ui/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ django-cors-headers = "3.9"
django-redis = "^5.2.0"
pygtrans = "^1.4.0"
astunparse = "^1.6.3"
blueapps = { version = "4.15.8", extras = ["opentelemetry"]}
blueapps = { version = "4.16rc4", extras = ["opentelemetry"]}
pyinstrument = "3.4.2"
mistune = "0.8.4"
bkoauth = "0.1.0"
Expand Down