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
34 changes: 31 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,34 @@ def _set_cache(self, cache_key, data):
"""
cache.set(cache_key, data, self.cache_time)

def _get_cached_admin_username(self):
"""
获取缓存的租户管理员用户名
"""
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, "bk_username": "bk_admin"}
data = UserManagerApi.batch_lookup_virtual_user(params, use_admin=False, use_param_user=True)
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 @@ -437,10 +465,10 @@ def _set_session_headers(self, session, local_request, headers: Dict, params: Di
}
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="v1/channels/",
description=_("查询通知类型"),
)
self.send_voice = self.generate_data_api(
method="POST",
url="v1/send_voice/",
description=_("语音通知"),
)
self.send_sms = self.generate_data_api(
method="POST",
url="v1/send_sms/",
description=_("短信通知"),
)
self.send_weixin = self.generate_data_api(
method="POST",
url="v1/send_weixin/",
description=_("微信通知"),
)
self.send_mail = self.generate_data_api(
method="POST",
url="v1/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()
12 changes: 9 additions & 3 deletions dbm-ui/backend/components/domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,24 @@

from backend import env

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

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

# apigw
APIGW_PREFIX = "/api/{}/prod/"
APIGW_DOMAIN_TPL = "{}{}".format(env.BASE_APIGW_DOMAIN, APIGW_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")
JOB_APIGW_DOMAIN = env.JOB_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("jobv3")
SOPS_APIGW_DOMAIN = env.SOPS_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("sops")
ESB_APIGW_DOMAIN = env.ESB_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("esb")
USER_MANAGE_APIGW_DOMAIN = env.USER_MANAGE_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("usermanage")
CMSI_APIGW_DOMAIN = env.CMSI_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("cmsi")
# 用户管理
USER_MANAGE_APIGW_DOMAIN = APIGW_DOMAIN_TPL.format("bk-user") or ESB_DOMAIN_TPL.format("usermanage")
# 消息通知
CMSI_APIGW_DOMAIN = APIGW_DOMAIN_TPL.format("bk-cmsi") or ESB_DOMAIN_TPL.format("cmsi")
BKCHAT_APIGW_DOMAIN = env.BKCHAT_APIGW_DOMAIN
ITSM_APIGW_DOMAIN = env.ITSM_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("itsm")
BKLOG_APIGW_DOMAIN = env.BKLOG_APIGW_DOMAIN or ESB_DOMAIN_TPL.format("bk_log")
Expand Down
9 changes: 7 additions & 2 deletions dbm-ui/backend/components/usermanage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@ class _UserManageApi(BaseApi):
def __init__(self):
self.list_users = self.generate_data_api(
method="GET",
url="list_users/",
url="api/v3/open/tenant/users/",
description=_("获取所有用户"),
cache_time=300,
)
self.retrieve_user = self.generate_data_api(
method="GET",
url="retrieve_user/",
url="api/v3/open/tenant/users/{bk_username}/",
description=_("获取单个用户"),
)
self.batch_lookup_virtual_user = self.generate_data_api(
method="GET",
url="api/v3/open/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
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/env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
# esb 访问地址
BK_COMPONENT_API_URL = get_type_env(key="BK_COMPONENT_API_URL", _type=str, default="https://bk-component.example.com")

# apigw 访问地址
BASE_APIGW_DOMAIN = get_type_env(key="BASE_APIGW_DOMAIN", _type=str, default="https://bkapi.bk-component.example.com")

# 开启外部路由,供外部环境使用(DBConsole)
ENABLE_EXTERNAL_PROXY = get_type_env(key="ENABLE_EXTERNAL_PROXY", _type=bool, default=False)
# 开启所有路由,不屏蔽。!!这里只用在合作伙伴环境!!
Expand Down