From 297270063c04c7ddab31130b7ed9456825bd7a02 Mon Sep 17 00:00:00 2001 From: simontigers <47096077+simontigers@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:32:20 +0800 Subject: [PATCH] feat: notice_config access messenger (#190) --- cmdb-api/api/commands/click_common_setting.py | 2 +- .../api/lib/common_setting/company_info.py | 28 +++- cmdb-api/api/lib/common_setting/const.py | 7 + cmdb-api/api/lib/common_setting/employee.py | 55 ++++++- .../api/lib/common_setting/notice_config.py | 143 +++++++++++++----- .../api/lib/common_setting/resp_format.py | 7 + cmdb-api/api/views/common_setting/employee.py | 12 ++ .../api/views/common_setting/notice_config.py | 8 + 8 files changed, 220 insertions(+), 42 deletions(-) diff --git a/cmdb-api/api/commands/click_common_setting.py b/cmdb-api/api/commands/click_common_setting.py index f886e9b..14bb2c8 100644 --- a/cmdb-api/api/commands/click_common_setting.py +++ b/cmdb-api/api/commands/click_common_setting.py @@ -177,7 +177,7 @@ class InitDepartment(object): else: resource_type = results[0] - for name in ['公司信息']: + for name in ['公司信息', '通知设置']: payload = dict( type_id=resource_type['id'], app_id=acl.app_name, diff --git a/cmdb-api/api/lib/common_setting/company_info.py b/cmdb-api/api/lib/common_setting/company_info.py index 861bb3f..c700fea 100644 --- a/cmdb-api/api/lib/common_setting/company_info.py +++ b/cmdb-api/api/lib/common_setting/company_info.py @@ -1,5 +1,5 @@ # -*- coding:utf-8 -*- - +from api.extensions import cache from api.models.common_setting import CompanyInfo @@ -11,14 +11,34 @@ class CompanyInfoCRUD(object): @staticmethod def create(**kwargs): - return CompanyInfo.create(**kwargs) + res = CompanyInfo.create(**kwargs) + CompanyInfoCache.refresh(res.info) + return res @staticmethod def update(_id, **kwargs): kwargs.pop('id', None) existed = CompanyInfo.get_by_id(_id) if not existed: - return CompanyInfoCRUD.create(**kwargs) + existed = CompanyInfoCRUD.create(**kwargs) else: existed = existed.update(**kwargs) - return existed + CompanyInfoCache.refresh(existed.info) + return existed + + +class CompanyInfoCache(object): + key = 'CompanyInfoCache::' + + @classmethod + def get(cls): + info = cache.get(cls.key) + if not info: + res = CompanyInfo.get_by(first=True) or {} + info = res.get('info', {}) + cache.set(cls.key, info) + return info + + @classmethod + def refresh(cls, info): + cache.set(cls.key, info) \ No newline at end of file diff --git a/cmdb-api/api/lib/common_setting/const.py b/cmdb-api/api/lib/common_setting/const.py index 2768f56..bb585d6 100644 --- a/cmdb-api/api/lib/common_setting/const.py +++ b/cmdb-api/api/lib/common_setting/const.py @@ -12,3 +12,10 @@ class OperatorType(BaseEnum): LESS_THAN = 6 IS_EMPTY = 7 IS_NOT_EMPTY = 8 + + +BotNameMap = { + 'wechatApp': 'wechatBot', + 'feishuApp': 'feishuBot', + 'dingdingApp': 'dingdingBot', +} diff --git a/cmdb-api/api/lib/common_setting/employee.py b/cmdb-api/api/lib/common_setting/employee.py index bbd3cb6..e642ccc 100644 --- a/cmdb-api/api/lib/common_setting/employee.py +++ b/cmdb-api/api/lib/common_setting/employee.py @@ -1,8 +1,9 @@ # -*- coding:utf-8 -*- - +import copy import traceback from datetime import datetime +import requests from flask import abort from flask_login import current_user from sqlalchemy import or_, literal_column, func, not_, and_ @@ -474,6 +475,58 @@ class EmployeeCRUD(object): return [r.to_dict() for r in results] + @staticmethod + def remove_bind_notice_by_uid(_platform, _uid): + existed = EmployeeCRUD.get_employee_by_uid(_uid) + employee_data = existed.to_dict() + + notice_info = copy.deepcopy(employee_data.get('notice_info', {})) + + notice_info[_platform] = '' + + existed.update( + notice_info=notice_info + ) + return ErrFormat.notice_remove_bind_success + + @staticmethod + def bind_notice_by_uid(_platform, _uid): + existed = EmployeeCRUD.get_employee_by_uid(_uid) + mobile = existed.mobile + if not mobile or len(mobile) == 0: + abort(400, ErrFormat.notice_bind_err_with_empty_mobile) + + from api.lib.common_setting.notice_config import NoticeConfigCRUD + messenger = NoticeConfigCRUD.get_messenger_url() + if not messenger or len(messenger) == 0: + abort(400, ErrFormat.notice_please_config_messenger_first) + + url = f"{messenger}/v1/uid/getbyphone" + try: + payload = dict( + phone=mobile, + sender=_platform + ) + res = requests.post(url, json=payload) + result = res.json() + if res.status_code != 200: + raise Exception(result.get('msg', '')) + target_id = result.get('uid', '') + + employee_data = existed.to_dict() + + notice_info = copy.deepcopy(employee_data.get('notice_info', {})) + + notice_info[_platform] = '' if not target_id else target_id + + existed.update( + notice_info=notice_info + ) + return ErrFormat.notice_bind_success + + except Exception as e: + return abort(400, ErrFormat.notice_bind_failed.format(str(e))) + @staticmethod def get_employee_notice_by_ids(employee_ids): criterion = [ diff --git a/cmdb-api/api/lib/common_setting/notice_config.py b/cmdb-api/api/lib/common_setting/notice_config.py index af8ff87..569ac35 100644 --- a/cmdb-api/api/lib/common_setting/notice_config.py +++ b/cmdb-api/api/lib/common_setting/notice_config.py @@ -1,41 +1,104 @@ -from api.models.common_setting import NoticeConfig +import requests + +from api.lib.common_setting.const import BotNameMap +from api.lib.common_setting.resp_format import ErrFormat +from api.models.common_setting import CompanyInfo, NoticeConfig from wtforms import Form from wtforms import StringField from wtforms import validators -from flask import abort -import smtplib -from email.mime.text import MIMEText -from email.utils import formataddr +from flask import abort, current_app class NoticeConfigCRUD(object): @staticmethod def add_notice_config(**kwargs): - NoticeConfigCRUD.check_platform(kwargs.get('platform')) + platform = kwargs.get('platform') + NoticeConfigCRUD.check_platform(platform) + info = kwargs.get('info', {}) + if 'name' not in info: + info['name'] = platform + kwargs['info'] = info try: - return NoticeConfig.create( + NoticeConfigCRUD.update_messenger_config(**info) + res = NoticeConfig.create( **kwargs ) + return res except Exception as e: return abort(400, str(e)) @staticmethod def check_platform(platform): - NoticeConfig.get_by(first=True, to_dict=False, platform=platform) and abort(400, f"{platform} 已存在!") + NoticeConfig.get_by(first=True, to_dict=False, platform=platform) and \ + abort(400, ErrFormat.notice_platform_existed.format(platform)) @staticmethod def edit_notice_config(_id, **kwargs): existed = NoticeConfigCRUD.get_notice_config_by_id(_id) try: - return existed.update(**kwargs) + info = kwargs.get('info', {}) + if 'name' not in info: + info['name'] = existed.platform + kwargs['info'] = info + NoticeConfigCRUD.update_messenger_config(**info) + + res = existed.update(**kwargs) + return res + except Exception as e: + return abort(400, str(e)) + + @staticmethod + def get_messenger_url(): + from api.lib.common_setting.company_info import CompanyInfoCache + com_info = CompanyInfoCache.get() + if not com_info: + return + messenger = com_info.get('messenger', '') + if len(messenger) == 0: + return + if messenger[-1] == '/': + messenger = messenger[:-1] + return messenger + + @staticmethod + def update_messenger_config(**kwargs): + try: + messenger = NoticeConfigCRUD.get_messenger_url() + if not messenger or len(messenger) == 0: + raise Exception(ErrFormat.notice_please_config_messenger_first) + + url = f"{messenger}/v1/senders" + name = kwargs.get('name') + bot_list = kwargs.pop('bot', None) + for k, v in kwargs.items(): + if isinstance(v, bool): + kwargs[k] = 'true' if v else 'false' + else: + kwargs[k] = str(v) + + payload = {name: [kwargs]} + current_app.logger.info(f"update_messenger_config: {url}, {payload}") + res = requests.put(url, json=payload, timeout=2) + current_app.logger.info(f"update_messenger_config: {res.status_code}, {res.text}") + + if not bot_list or len(bot_list) == 0: + return + bot_name = BotNameMap.get(name) + payload = {bot_name: bot_list} + current_app.logger.info(f"update_messenger_config: {url}, {payload}") + bot_res = requests.put(url, json=payload, timeout=2) + current_app.logger.info(f"update_messenger_config: {bot_res.status_code}, {bot_res.text}") + except Exception as e: return abort(400, str(e)) @staticmethod def get_notice_config_by_id(_id): - return NoticeConfig.get_by(first=True, to_dict=False, id=_id) or abort(400, f"{_id} 配置项不存在!") + return NoticeConfig.get_by(first=True, to_dict=False, id=_id) or \ + abort(400, + ErrFormat.notice_not_existed.format(_id)) @staticmethod def get_all(): @@ -43,38 +106,46 @@ class NoticeConfigCRUD(object): @staticmethod def test_send_email(receive_address, **kwargs): - # 设置发送方和接收方的电子邮件地址 - sender_email = 'test@test.com' - sender_name = 'Test Sender' + messenger = NoticeConfigCRUD.get_messenger_url() + if not messenger or len(messenger) == 0: + abort(400, ErrFormat.notice_please_config_messenger_first) + url = f"{messenger}/v1/message" + recipient_email = receive_address - recipient_name = receive_address subject = 'Test Email' body = 'This is a test email' - - message = MIMEText(body, 'plain', 'utf-8') - message['From'] = formataddr((sender_name, sender_email)) - message['To'] = formataddr((recipient_name, recipient_email)) - message['Subject'] = subject - - smtp_server = kwargs.get('server') - smtp_port = kwargs.get('port') - smtp_username = kwargs.get('username') - smtp_password = kwargs.get('password') - - if kwargs.get('mail_type') == 'SMTP': - smtp_connection = smtplib.SMTP(smtp_server, smtp_port) - else: - smtp_connection = smtplib.SMTP_SSL(smtp_server, smtp_port) - - if kwargs.get('is_login'): - smtp_connection.login(smtp_username, smtp_password) - - smtp_connection.sendmail(sender_email, recipient_email, message.as_string()) - smtp_connection.quit() + payload = { + "sender": 'email', + "msgtype": "text/plain", + "title": subject, + "content": body, + "tos": [recipient_email], + } + current_app.logger.info(f"test_send_email: {url}, {payload}") + response = requests.post(url, json=payload) + if response.status_code != 200: + abort(400, response.text) return 1 + @staticmethod + def get_app_bot(): + result = [] + for notice_app in NoticeConfig.get_by(to_dict=False): + if notice_app.platform in ['email']: + continue + info = notice_app.info + name = info.get('name', '') + if name not in BotNameMap: + continue + result.append(dict( + name=info.get('name', ''), + label=info.get('label', ''), + bot=info.get('bot', []), + )) + return result + class NoticeConfigForm(Form): platform = StringField(validators=[ @@ -91,4 +162,4 @@ class NoticeConfigUpdateForm(Form): info = StringField(validators=[ validators.DataRequired(message="信息 不能为空"), validators.Length(max=255), - ]) + ]) \ No newline at end of file diff --git a/cmdb-api/api/lib/common_setting/resp_format.py b/cmdb-api/api/lib/common_setting/resp_format.py index a9c6a35..4c2d6f7 100644 --- a/cmdb-api/api/lib/common_setting/resp_format.py +++ b/cmdb-api/api/lib/common_setting/resp_format.py @@ -56,3 +56,10 @@ class ErrFormat(CommonErrFormat): email_send_timeout = "邮件发送超时" common_data_not_found = "ID {} 找不到记录" + notice_platform_existed = "{} 已存在" + notice_not_existed = "{} 配置项不存在" + notice_please_config_messenger_first = "请先配置 messenger" + notice_bind_err_with_empty_mobile = "绑定失败,手机号为空" + notice_bind_failed = "绑定失败: {}" + notice_bind_success = "绑定成功" + notice_remove_bind_success = "解绑成功" diff --git a/cmdb-api/api/views/common_setting/employee.py b/cmdb-api/api/views/common_setting/employee.py index ce5578b..e31ee59 100644 --- a/cmdb-api/api/views/common_setting/employee.py +++ b/cmdb-api/api/views/common_setting/employee.py @@ -156,3 +156,15 @@ class GetEmployeeNoticeByIds(APIView): else: result = EmployeeCRUD.get_employee_notice_by_ids(employee_ids) return self.jsonify(result) + + +class EmployeeBindNoticeWithACLID(APIView): + url_prefix = (f'{prefix}/by_uid/bind_notice//',) + + def put(self, platform, _uid): + data = EmployeeCRUD.bind_notice_by_uid(platform, _uid) + return self.jsonify(info=data) + + def delete(self, platform, _uid): + data = EmployeeCRUD.remove_bind_notice_by_uid(platform, _uid) + return self.jsonify(info=data) diff --git a/cmdb-api/api/views/common_setting/notice_config.py b/cmdb-api/api/views/common_setting/notice_config.py index 6f09196..49273cf 100644 --- a/cmdb-api/api/views/common_setting/notice_config.py +++ b/cmdb-api/api/views/common_setting/notice_config.py @@ -69,3 +69,11 @@ class NoticeConfigGetView(APIView): def get(self): res = NoticeConfigCRUD.get_all() return self.jsonify(res) + + +class NoticeAppBotView(APIView): + url_prefix = (f'{prefix}/app_bot',) + + def get(self): + res = NoticeConfigCRUD.get_app_bot() + return self.jsonify(res)