Skip to content

Commit 86171ed

Browse files
committed
feat(backend): bkchat专属process todo TencentBlueKing#8755
1 parent 8ea720e commit 86171ed

File tree

11 files changed

+112
-33
lines changed

11 files changed

+112
-33
lines changed

dbm-ui/backend/core/notify/handlers.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
from backend.core.notify.exceptions import NotifyBaseException
2525
from backend.core.notify.template import FAILED_TEMPLATE, FINISHED_TEMPLATE, TERMINATE_TEMPLATE, TODO_TEMPLATE
2626
from backend.db_meta.models import AppCache
27+
from backend.exceptions import ApiResultError
2728
from backend.ticket.builders import BuilderFactory
28-
from backend.ticket.constants import TicketStatus, TicketType
29+
from backend.ticket.constants import TicketStatus, TicketType, TodoStatus
2930
from backend.ticket.models import Flow, Ticket
3031
from backend.ticket.todos import ActionType
3132
from backend.utils.cache import func_cache_decorator
32-
from backend.utils.time import datetime2str
3333

3434
logger = logging.getLogger("root")
3535

@@ -78,20 +78,25 @@ def get_actions(msg_type, ticket):
7878
"""获取bkchat操作按钮"""
7979
if ticket.status not in [TicketStatus.APPROVE, TicketStatus.TODO]:
8080
return []
81+
82+
todo = ticket.todo_of_ticket.filter(status=TodoStatus.TODO).first()
83+
if not todo:
84+
return []
85+
8186
# 增加回调按钮,执行和终止
8287
agree_action = {
8388
"name": _("同意") if ticket.status == TicketStatus.APPROVE else _("确认执行"),
8489
"color": "green",
85-
"callback_url": f"{env.BK_DBM_APIGATEWAY}/tickets/batch_process_ticket/",
86-
"callback_data": {"action": ActionType.APPROVE.value, "ticket_ids": [ticket.id]},
90+
"callback_url": f"{env.BK_DBM_APIGATEWAY}/tickets/bkchat_process_todo/",
91+
"callback_data": {"action": ActionType.APPROVE.value, "todo_id": todo.id, "params": {}},
8792
}
8893
refuse_action = {
8994
"name": _("拒绝") if ticket.status == TicketStatus.APPROVE else _("终止单据"),
9095
"color": "red",
91-
"callback_url": f"{env.BK_DBM_APIGATEWAY}/tickets/batch_process_ticket/",
96+
"callback_url": f"{env.BK_DBM_APIGATEWAY}/tickets/bkchat_process_todo/",
9297
"callback_data": {
9398
"action": ActionType.TERMINATE.value,
94-
"ticket_ids": [ticket.id],
99+
"todo_id": todo.id,
95100
"params": {"remark": _("使用「蓝鲸审批助手」终止单据")},
96101
},
97102
}
@@ -238,7 +243,11 @@ def __init__(self, ticket_id: int, flow_id: int = None):
238243
def get_support_msg_types(cls):
239244
# 获取当前环境下支持的通知类型
240245
# 所有的拓展方式都需要接入CMSI,所以直接返回CMSI支持方式即可
241-
return CmsiApi.get_msg_type()
246+
# 暂不暴露微信的通知方式
247+
msg_types = CmsiApi.get_msg_type()
248+
msg_type_map = {msg["type"]: msg for msg in msg_types}
249+
msg_type_map[MsgType.WEIXIN.value]["is_active"] = False
250+
return list(msg_type_map.values())
242251

243252
def get_notify_class(self, msg_type: str):
244253
# 根据通知类型获取通知类,以及通知所需的上下文
@@ -253,15 +262,17 @@ def get_receivers(self):
253262
biz_helpers = BizSettings.get_assistance(self.bk_biz_id)
254263
creator = [self.ticket.creator]
255264
# 待审批:审批人
256-
# 待执行、待补货、待确认、已失败、已完成、已终止: 提单人、协助人
265+
# 待执行、待补货、待确认、已失败、已完成、已终止:提单人、协助人
257266
# 暂不通知DBA
258267
if self.phase in [TicketStatus.PENDING]:
259-
return creator
268+
receivers = creator
260269
elif self.phase in [TicketStatus.APPROVE]:
261270
itsm_builder = BuilderFactory.get_builder_cls(self.ticket.ticket_type).itsm_flow_builder(self.ticket)
262-
return itsm_builder.get_approvers().split(",")
271+
receivers = itsm_builder.get_approvers().split(",")
263272
else:
264-
return creator + biz_helpers
273+
receivers = creator + biz_helpers
274+
# 去重后返回
275+
return list(set(receivers))
265276

266277
def render_msg_template(self, msg_type: str):
267278
# 获取标题,在群机器人通知则加上@人
@@ -289,8 +300,8 @@ def render_msg_template(self, msg_type: str):
289300
"cluster_domains": ",".join(self.clusters),
290301
"remark": self.ticket.remark,
291302
"creator": self.ticket.creator,
292-
"submit_time": datetime2str(self.ticket.create_at),
293-
"update_time": datetime2str(self.ticket.update_at),
303+
"submit_time": self.ticket.create_at.astimezone().strftime("%Y-%m-%d %H:%M:%S%z"),
304+
"update_time": self.ticket.update_at.astimezone().strftime("%Y-%m-%d %H:%M:%S%z"),
294305
"status": TicketStatus.get_choice_label(self.phase),
295306
"operators": ",".join(self.ticket.get_current_operators()),
296307
"detail_address": self.ticket.url,
@@ -325,7 +336,10 @@ def send_msg(self):
325336
if msg_type == MsgType.WECOM_ROBOT:
326337
self.receivers = send_msg_config.get(MsgType.WECOM_ROBOT.value, [])
327338

328-
notify_class(title, content, self.receivers).send_msg(msg_type, context=context)
339+
try:
340+
notify_class(title, content, self.receivers).send_msg(msg_type, context=context)
341+
except (ApiResultError, Exception) as e:
342+
logger.error(_("消息发送失败,错误信息: {}").format(e))
329343

330344

331345
@shared_task

dbm-ui/backend/db_services/plugin/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
specific language governing permissions and limitations under the License.
1010
"""
1111

12-
SWAGGER_TAG = "plugin"
12+
SWAGGER_TAG = "OpenAPI"

dbm-ui/backend/db_services/plugin/ticket/serializers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,17 @@
1212
from django.utils.translation import gettext_lazy as _
1313
from rest_framework import serializers
1414

15-
from backend.ticket.serializers import BatchTicketOperateSerializer
15+
from backend.ticket.serializers import BatchTicketOperateSerializer, TodoOperateSerializer
1616

1717

1818
class OpenAPIBatchTicketOperateSerializer(BatchTicketOperateSerializer):
1919
username = serializers.CharField(help_text=_("操作者"))
20+
21+
22+
class OpenAPIBkChatProcessTodoSerializer(TodoOperateSerializer):
23+
username = serializers.CharField(help_text=_("操作者"))
24+
25+
26+
class OpenAPIBkChatProcessTodoResponseSerializer(serializers.Serializer):
27+
response_msg = serializers.CharField(help_text=_("返回信息"))
28+
response_color = serializers.CharField(help_text=_("按钮颜色"))

dbm-ui/backend/db_services/plugin/ticket/views.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
from rest_framework.response import Response
1818

1919
from backend.db_services.plugin.constants import SWAGGER_TAG
20-
from backend.db_services.plugin.ticket.serializers import OpenAPIBatchTicketOperateSerializer
20+
from backend.db_services.plugin.ticket.serializers import (
21+
OpenAPIBatchTicketOperateSerializer,
22+
OpenAPIBkChatProcessTodoResponseSerializer,
23+
OpenAPIBkChatProcessTodoSerializer,
24+
)
2125
from backend.db_services.plugin.view import BaseOpenAPIViewSet
26+
from backend.ticket.constants import TodoStatus, TodoType
27+
from backend.ticket.exceptions import TodoDuplicateProcessException
2228
from backend.ticket.handler import TicketHandler
23-
from backend.ticket.serializers import TodoSerializer
29+
from backend.ticket.models import Todo
30+
from backend.ticket.todos import TodoActorFactory
2431

2532
logger = logging.getLogger("root")
2633

@@ -29,10 +36,39 @@ class TicketViewSet(BaseOpenAPIViewSet):
2936
@swagger_auto_schema(
3037
operation_summary=_("批量单据待办处理"),
3138
request_body=OpenAPIBatchTicketOperateSerializer(),
32-
responses={status.HTTP_200_OK: TodoSerializer(many=True)},
3339
tags=[SWAGGER_TAG],
3440
)
3541
@action(methods=["POST"], detail=False, serializer_class=OpenAPIBatchTicketOperateSerializer)
3642
def batch_process_ticket(self, request, *args, **kwargs):
3743
params = self.params_validate(self.get_serializer_class())
3844
return Response(TicketHandler.batch_process_ticket(**params))
45+
46+
@swagger_auto_schema(
47+
operation_summary=_("待办处理(bkchat专属)"),
48+
request_body=OpenAPIBkChatProcessTodoSerializer(),
49+
responses={status.HTTP_200_OK: OpenAPIBkChatProcessTodoResponseSerializer()},
50+
tags=[SWAGGER_TAG],
51+
)
52+
@action(methods=["POST"], detail=False, serializer_class=OpenAPIBkChatProcessTodoSerializer)
53+
def bkchat_process_todo(self, request, *args, **kwargs):
54+
"""
55+
bkchat专属的待办处理,区别主要是返回结构不同
56+
"""
57+
params = self.params_validate(self.get_serializer_class())
58+
59+
todo = Todo.objects.get(id=params["todo_id"])
60+
if todo.type not in [TodoType.ITSM, TodoType.APPROVE]:
61+
return Response({"response_msg": _("暂不支持该类型{}todo的处理").fromat(todo.type), "response_color": "red"})
62+
63+
# 确认todo,忽略重复操作
64+
try:
65+
TodoActorFactory.actor(todo).process(params["username"], params["action"], params["params"])
66+
except TodoDuplicateProcessException:
67+
pass
68+
69+
# 根据操作类型获取文案和按钮颜色
70+
todo.refresh_from_db()
71+
if todo.status == TodoStatus.DONE_FAILED:
72+
return Response({"response_msg": _("{} 已终止").format(todo.done_by), "response_color": "red"})
73+
elif todo.status == TodoStatus.DONE_SUCCESS:
74+
return Response({"response_msg": _("{} 已确认").format(todo.done_by), "response_color": "green"})

dbm-ui/backend/ticket/exceptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,19 @@ class TodoWrongOperatorException(TicketBaseException):
6060
MESSAGE_TPL = _("错误的todo处理人{username}")
6161

6262

63+
class TodoDuplicateProcessException(TicketBaseException):
64+
ERROR_CODE = "010"
65+
MESSAGE = _("重复操作")
66+
MESSAGE_TPL = _("重复操作")
67+
68+
6369
class ApprovalWrongOperatorException(TicketBaseException):
6470
ERROR_CODE = "008"
6571
MESSAGE = _("审批处理异常")
6672
MESSAGE_TPL = _("审批处理异常{username}")
6773

6874

6975
class TicketFlowsConfigException(TicketBaseException):
70-
ERROR_CODE = "008"
76+
ERROR_CODE = "009"
7177
MESSAGE = _("单据流程设置失败")
7278
MESSAGE_TPL = _("单据流程{ticket_type}设置失败")

dbm-ui/backend/ticket/flow_manager/manager.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"""
1111
import logging
1212

13+
from django.db import transaction
14+
1315
from backend import env
1416
from backend.core import notify
1517
from backend.ticket import constants
@@ -115,15 +117,20 @@ def update_ticket_status(self):
115117
# 其他场景下状态未变更,无需更新DB
116118
return
117119

118-
if self.ticket.status != target_status:
119-
origin_status = self.ticket.status
120-
self.ticket.status = target_status
121-
self.ticket.save(update_fields=["status", "update_at"])
120+
# 原子更新单据状态
121+
with transaction.atomic():
122+
ticket = Ticket.objects.select_for_update().get(id=self.ticket.id)
123+
if ticket.status == target_status:
124+
return
125+
origin_status, ticket.status = ticket.status, target_status
126+
ticket.save(update_fields=["status", "update_at"])
122127
self.ticket_status_trigger(origin_status, target_status)
123128

124129
def ticket_status_trigger(self, origin_status, target_status):
125130
"""单据状态更新后的钩子函数"""
126131

127-
# 单据状态变更后,发送通知。忽略running
128-
if target_status != TicketStatus.RUNNING:
132+
# 单据状态变更后,发送通知。
133+
# 忽略运行中:流转到内置任务无需通知,待继续在todo创建时才触发通知
134+
# 忽略待补货:到资源申请节点,单据状态总会流转为待补货,但是只有待补货todo创建才触发通知
135+
if target_status not in [TicketStatus.RUNNING, TicketStatus.RESOURCE_REPLENISH]:
129136
notify.send_msg.apply_async(args=(self.ticket.id,))

dbm-ui/backend/ticket/flow_manager/resource.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from backend.components.dbresource.client import DBResourceApi
2222
from backend.configuration.constants import AffinityEnum
2323
from backend.configuration.models import DBAdministrator
24+
from backend.core import notify
2425
from backend.db_meta.models import Spec
2526
from backend.db_services.dbresource.exceptions import ResourceApplyException, ResourceApplyInsufficientException
2627
from backend.db_services.ipchooser.constants import CommonEnum
@@ -214,6 +215,7 @@ def create_replenish_todo(self):
214215
flow_id=self.flow_obj.id, ticket_id=self.ticket.id, user=self.ticket.creator, administrators=dba
215216
).to_dict(),
216217
)
218+
notify.send_msg.apply_async(args=(self.ticket.id,))
217219

218220
def fetch_apply_params(self, ticket_data):
219221
"""

dbm-ui/backend/ticket/handler.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,19 +231,24 @@ def approve_itsm_ticket(cls, ticket_id, action, operator, **kwargs):
231231
act_msg = kwargs.get("action_message") or act_msg_tpl
232232

233233
# 审批单据
234-
params = {"action_message": act_msg}
234+
params = {
235+
"sn": sn,
236+
"action_message": act_msg,
237+
"action_type": action,
238+
"operator": operator,
239+
"bk_username": operator,
240+
}
235241
if action == OperateNodeActionType.TRANSITION:
236242
is_approved = kwargs["is_approved"]
237243
itsm_fields = cls.get_itsm_fields(flow.ticket.ticket_type)
238244
fields = [
239245
{"key": itsm_fields[0], "value": json.dumps(is_approved)},
240246
{"key": itsm_fields[1], "value": act_msg},
241247
]
242-
params.update(sn=sn, state_id=state_id, action_type=action, operator=operator, fields=fields)
248+
params.update(state_id=state_id, fields=fields)
243249
ItsmApi.operate_node(params)
244250
# 终止/撤销单据
245251
elif action in [OperateNodeActionType.TERMINATE, OperateNodeActionType.WITHDRAW]:
246-
params.update(sn=sn, action_type=action, operator=operator)
247252
ItsmApi.operate_ticket(params)
248253

249254
return sn

dbm-ui/backend/ticket/todos/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from backend.constants import DEFAULT_SYSTEM_USER
2121
from backend.ticket.constants import TODO_RUNNING_STATUS
22-
from backend.ticket.exceptions import TodoWrongOperatorException
22+
from backend.ticket.exceptions import TodoDuplicateProcessException, TodoWrongOperatorException
2323
from backend.ticket.models import Todo
2424
from blue_krill.data_types.enum import EnumField, StructuredEnum
2525

@@ -55,7 +55,7 @@ def allow_superuser_process(self):
5555
def process(self, username, action, params):
5656
# 当状态已经被确认,则不允许重复操作
5757
if self.todo.status not in TODO_RUNNING_STATUS:
58-
raise TodoWrongOperatorException(_("当前代办操作已经处理,不能重复处理!"))
58+
raise TodoDuplicateProcessException(_("当前代办操作已经处理,不能重复处理!"))
5959

6060
# 允许系统内置用户确认
6161
if username == DEFAULT_SYSTEM_USER:

dbm-ui/backend/ticket/todos/pipeline_todo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def create(cls, ticket, flow, root_id, node_id):
7474

7575
# 当前不存在待确认的todo,则发送通知
7676
if not flow.todo_of_flow.filter(type=TodoType.INNER_APPROVE).count():
77-
notify.send_msg(ticket.id, flow.id)
77+
notify.send_msg.apply_async(args=(ticket.id,))
7878

7979
Todo.objects.create(
8080
name=_("【{}】流程待确认,是否继续?").format(ticket.get_ticket_type_display()),

dbm-ui/backend/utils/time.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def timezone2timestamp(date: Union[str, datetime.datetime]) -> int:
4343
return int(time_parse(date).timestamp())
4444

4545

46-
def datetime2str(o_datetime: datetime.datetime, fmt: str = DATETIME_PATTERN, aware_check: bool = True) -> str:
46+
def datetime2str(o_datetime: datetime.datetime, aware_check: bool = True) -> str:
4747
"""
4848
将时间对象转换为时间字符串,可选时区强校验
4949
"""

0 commit comments

Comments
 (0)