From 9b85a0ad13632a7752ff2a8bc98f75894fa3fa44 Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:20:30 +0800 Subject: [PATCH 1/6] upload lib/exception.py --- sysom_server/sysom_hotfix/lib/exception.py | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/exception.py diff --git a/sysom_server/sysom_hotfix/lib/exception.py b/sysom_server/sysom_hotfix/lib/exception.py new file mode 100644 index 00000000..f660a9e5 --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/exception.py @@ -0,0 +1,63 @@ +import logging +import traceback + +from django.db.models import ProtectedError +from rest_framework.views import set_rollback +from rest_framework import exceptions, status +from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, NotAuthenticated + +from .response import ErrorResponse + + +logger = logging.getLogger(__name__) + + +class APIException(Exception): + def __init__(self, code=400, message='API异常', args=('API异常',)): + self.code = code + self.message = message + self.args = args + + def __str__(self): + return self.message + + +class FileNotFoundException(Exception): + def __init__(self, code=404, message='文件不存在'): + self.code = code + self.message = message + + def __str__(self): + return self.message + + +def exception_handler(exc, context): + """自定义异常处理""" + msg = '' + code = 400 + + if isinstance(exc, FileNotFoundException): + code = exc.code + msg = exc.message + if isinstance(exc, AuthenticationFailed): + code = 403 + msg = exc.detail + elif isinstance(exc, NotAuthenticated): + code = 402 + msg = exc.detail + elif isinstance(exc, DRFAPIException): + set_rollback() + # print(exc.detail) + # msg = {str(e) for e in exc.detail} + msg = exc.detail + elif isinstance(exc, exceptions.APIException): + set_rollback() + msg = exc.detail + elif isinstance(exc, ProtectedError): + set_rollback() + msg = "删除失败:该条数据与其他数据有相关绑定" + elif isinstance(exc, Exception): + logger.error(traceback.format_exc()) + msg = str(exc) # 原样输出错误 + + return ErrorResponse(msg=msg, code=code, status=code) -- Gitee From 39fdc28f27f57ea6c6126359fc0287a41fba309c Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:20:59 +0800 Subject: [PATCH 2/6] upload lib/paginations.py --- sysom_server/sysom_hotfix/lib/paginations.py | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/paginations.py diff --git a/sysom_server/sysom_hotfix/lib/paginations.py b/sysom_server/sysom_hotfix/lib/paginations.py new file mode 100644 index 00000000..b14639bb --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/paginations.py @@ -0,0 +1,26 @@ +''' +@File: paginations.py +@Time: 2021-12-14 13:46:02 +@Author: DM +@Desc: Local Paginations Class +''' + +from rest_framework.pagination import PageNumberPagination +from lib.response import success + + +class Pagination(PageNumberPagination): + page_query_param = "current" + page_size_query_param = "pageSize" + + def paginate_queryset(self, queryset, request, view=None): + self.max_page_size = queryset.count() + return super().paginate_queryset(queryset, request, view=view) + + def get_paginated_response(self, data): + return success(message="获取成功", result=data, total=self.page.paginator.count) + + def get_page_size(self, request): + if not request.query_params.get(self.page_size_query_param, None): + return self.max_page_size + return super().get_page_size(request) \ No newline at end of file -- Gitee From b1350d5ed18441134893994a5b46c3d91947e569 Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:21:40 +0800 Subject: [PATCH 3/6] upload lib/renderers.py --- sysom_server/sysom_hotfix/lib/renderers.py | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/renderers.py diff --git a/sysom_server/sysom_hotfix/lib/renderers.py b/sysom_server/sysom_hotfix/lib/renderers.py new file mode 100644 index 00000000..d6b4482f --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/renderers.py @@ -0,0 +1,42 @@ +import logging +from django.contrib.auth import get_user_model +from django.shortcuts import get_object_or_404 +from django.core.handlers.asgi import ASGIRequest +from rest_framework.renderers import JSONRenderer +from rest_framework.request import Request + + +logger = logging.getLogger(__name__) +# User = get_user_model() + + +class SysomJsonRender(JSONRenderer): + def render(self, data, accepted_media_type=None, renderer_context=None): + if renderer_context: + request = renderer_context.get('request', None) + view = renderer_context.get('view', None) + response = renderer_context.get('response', None) + self.before_response_save_log(request, view, response) + return super().render(data, accepted_media_type, renderer_context) + + def before_response_save_log(self, request: Request, view, response): + # user = getattr(request, 'user') or get_object_or_404(User, pk=1) + request: ASGIRequest = getattr(request, '_request', None) + method = request.method + + result = response.data + kwargs = { + 'request_ip': request.META.get('REMOTE_ADDR', None), + 'request_url': request.path, + 'request_browser_agent': request.headers.get('User-Agent', ''), + 'request_method': method, + 'handler_view': view.__class__.__name__, + 'response_status': getattr(response, 'status_code', 200), + } + if 'auth' in request.path: + kwargs['request_option'] = 0 + if result.get('code') == 200: + kwargs['user_id'] = result['data']['id'] + else: + kwargs['request_option'] = 1 + # kwargs['user'] = user -- Gitee From c0473d5cff4f3f2e4d633c10edcec469b990df31 Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:30:34 +0800 Subject: [PATCH 4/6] upload lib/response.py --- sysom_server/sysom_hotfix/lib/response.py | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/response.py diff --git a/sysom_server/sysom_hotfix/lib/response.py b/sysom_server/sysom_hotfix/lib/response.py new file mode 100644 index 00000000..a2311a42 --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/response.py @@ -0,0 +1,68 @@ +from rest_framework.response import Response +from rest_framework import status +from django.http import FileResponse + + +def _response(data=None, status=None): + return Response(data=data, status=status) + + +def success(result, message="success", success=True, code=status.HTTP_200_OK, **kwargs): + data = { + "code": code, + "message": message, + "data": result, + "success": success + } + data.update(kwargs) + return _response(data=data, status=code) + + +def not_found(code=status.HTTP_404_NOT_FOUND, success=False, message="Not Found"): + data = { + "code": code, + "message": message, + "success": success, + } + + return _response(data=data, status=code) + + +def not_permission(code=status.HTTP_403_FORBIDDEN, success=False, message="Not Permission"): + data = { + "code": code, + "success": success, + "message": message + } + return _response(data=data, status=code) + + +def other_response(result=dict(), message="", success=True, code=status.HTTP_200_OK, **kwargs): + data = { + "code": code, + "message": message, + "data": result, + "success": success + } + data.update(kwargs) + return _response(data=data, status=code) + + +class ErrorResponse(Response): + """ + 标准响应错误的返回,ErrorResponse(msg='xxx') + 默认错误码返回400, 也可以指定其他返回码:ErrorResponse(code=xxx) + """ + + def __init__(self, data=None, msg='error', code=400, status=None, template_name=None, headers=None, + exception=False, content_type=None): + std_data = { + "code": code, + "data": data or {}, + "message": msg + } + super().__init__(std_data, status, template_name, headers, exception, content_type) + + +class FileResponseAlter(FileResponse): + pass -- Gitee From bff962a3b6d37f15f56fe0c2573c725e3557bf4c Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:31:01 +0800 Subject: [PATCH 5/6] upload lib/ssh.py --- sysom_server/sysom_hotfix/lib/ssh.py | 100 +++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/ssh.py diff --git a/sysom_server/sysom_hotfix/lib/ssh.py b/sysom_server/sysom_hotfix/lib/ssh.py new file mode 100644 index 00000000..9de53fc4 --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/ssh.py @@ -0,0 +1,100 @@ +import logging +from typing import Callable +import paramiko +from io import StringIO +from paramiko.client import SSHClient, AutoAddPolicy +from paramiko.rsakey import RSAKey + +DEFAULT_CONNENT_TIMEOUT = 5 # 默认ssh链接超时时间 5s +DEFAULT_NODE_USER = 'root' # 默认节点用户名 root + +logger = logging.getLogger(__name__) + + +class SSH: + """A SSH client used to run command in remote node + + args: + hostname(str): Host name + + Keyword Args: + username(str): User name, default 'root' + port(str): SSH communicate port, default 22 + connect_timeout(int): Connection timeout duration, default 5s + password(str) + """ + + # key_pair cached the key pair generated by initialization stage + _key_pair = {} + _private_key_getter: Callable[[], str] = None + _public_key_getter: Callable[[], str] = None + + def __init__(self, hostname: str, **kwargs) -> None: + self.connect_args = { + 'hostname': hostname, + 'username': kwargs.get('username', DEFAULT_NODE_USER), + 'port': kwargs.get('port', 22), + 'timeout': kwargs.get('timeout', DEFAULT_CONNENT_TIMEOUT), + } + if 'password' in kwargs and kwargs['password'] is not None: + self.connect_args['password'] = kwargs.get('password') + else: + if SSH._private_key_getter is None: + raise Exception("_private_key_getter not set") + self.connect_args['pkey'] = RSAKey.from_private_key( + StringIO(SSH._private_key_getter()) + ) + + self._client: SSHClient = self.client() + + def client(self): + try: + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy) + client.connect(**self.connect_args) + return client + except paramiko.AuthenticationException: + raise Exception('authorization fail, password or pkey error!') + except: + raise Exception('authorization fail!') + + @classmethod + def set_private_key_getter(cls, private_key_getter: Callable[[], str]): + cls._private_key_getter = private_key_getter + + @classmethod + def set_public_key_getter(cls, public_key_getter: Callable[[], str]): + cls._public_key_getter = public_key_getter + + def run_command(self, command): + if self._client: + ssh_session = self._client.get_transport().open_session() + ssh_session.set_combine_stderr(True) + ssh_session.exec_command(command) + stdout = ssh_session.makefile("rb", -1) + statue = ssh_session.recv_exit_status() + output = stdout.read().decode() + return statue, output + else: + raise Exception('No client!') + + def add_public_key(self): + if self._public_key_getter is None: + raise Exception("_public_key_getter not set") + public_key = SSH._public_key_getter() + command = f'mkdir -p -m 700 ~/.ssh && \ + echo {public_key!r} >> ~/.ssh/authorized_keys && \ + chmod 600 ~/.ssh/authorized_keys' + statue, _ = self.run_command(command) + if statue != 0: + raise Exception('add public key faild!') + + @staticmethod + def validate_ssh_host(ip: str, password: str, port: int = 22, username: str = 'root'): + try: + ssh = SSH(hostname=ip, password=password, + port=port, username=username, timeout=2) + ssh.add_public_key() + return True, 'authorization success' + except Exception as e: + return False, f'error: {e}' -- Gitee From a827a00f2a9c2794c53cf8da63d4676dbb170e90 Mon Sep 17 00:00:00 2001 From: ydzhang Date: Fri, 6 Jan 2023 14:31:30 +0800 Subject: [PATCH 6/6] upload lib/utils.py --- sysom_server/sysom_hotfix/lib/utils.py | 198 +++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 sysom_server/sysom_hotfix/lib/utils.py diff --git a/sysom_server/sysom_hotfix/lib/utils.py b/sysom_server/sysom_hotfix/lib/utils.py new file mode 100644 index 00000000..c553c245 --- /dev/null +++ b/sysom_server/sysom_hotfix/lib/utils.py @@ -0,0 +1,198 @@ + +# -*- encoding: utf-8 -*- +""" +@File : utils.py +@Time : 2021/10/28 11:09 +@Author : DM +@Software: PyCharm +""" +import time +import uuid as UUID +from typing import List +import json +import logging +import jwt +import requests + +from importlib import import_module +from datetime import datetime, date as datetime_date +from decimal import Decimal + +from django.conf import settings +from apscheduler.schedulers.background import BackgroundScheduler +from paramiko.rsakey import RSAKey +from io import StringIO + + +logger = logging.getLogger(__name__) + +job_defaults = { + 'max_instances': 10, + 'misfire_grace_time': None, + 'coalesce': True, +} +scheduler = BackgroundScheduler(job_defaults=job_defaults) +scheduler.start() + + +CHAR_SET = ("a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", + "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z") + + +def human_datetime(date=None): + if date: + assert isinstance(date, datetime) + else: + date = datetime.now() + return date.strftime('%Y-%m-%d %H:%M:%S') + + +# 转换时间格式到字符串 +def datetime_str(date=None): + return datetime.strptime(date, "%Y-%m-%d %H:%M:%S") + + +# 日期json序列化 +class DateTimeEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, datetime): + return o.strftime('%Y-%m-%d %H:%M:%S') + elif isinstance(o, datetime_date): + return o.strftime('%Y-%m-%d') + elif isinstance(o, Decimal): + return float(o) + return json.JSONEncoder.default(self, o) + + +def get_request_real_ip(headers: dict): + x_real_ip = headers.get('x-forwarded-for') + if not x_real_ip: + x_real_ip = headers.get('x-real-ip', '') + return x_real_ip.split(',')[0] + + +def uuid_36(): + """ + 返回36字符的UUID字符串(十六进制,含有-) bc5debab-95c3-4430-933f-2e3b6407ac30 + :return: + """ + return str(UUID.uuid4()) + + +def uuid_32(): + """ + 返回32字符的UUID字符串(十六进制) bc5debab95c34430933f2e3b6407ac30 + :return: + """ + return uuid_36().replace('-', '') + + +def uuid_8(): + """ + 返回8字符的UUID字符串(非进制) 3FNWjtlD + :return: + """ + s = uuid_32() + result = '' + for i in range(0, 8): + sub = s[i * 4: i * 4 + 4] + x = int(sub, 16) + result += CHAR_SET[x % 0x3E] + return result + + +def url_format_dict(url_params: str): + """转化查询参数为dict""" + result = dict() + try: + for item in [{p.split('=')[0]: p.split('=')[1]} for p in url_params.split('&')]: + result.update(item) + except Exception as e: + logger.error(str(e)) + return result + + +def import_string(dotted_path: str): + """ + 优化import_module + Args: + dotted_path 动态导包路径 + Return Package + """ + try: + module_path, class_name = dotted_path.rsplit('.', 1) + except ValueError as err: + raise ImportError("%s doesn't look like a module path" % dotted_path) from err + module = import_module(dotted_path) + + try: + getattr(module, 'Channel') + return module + except AttributeError as err: + raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( + module_path, class_name) + ) from err + +def valid_params(require_params: dict, current_params: dict) -> List[str]: + missing_param_list = [] + for param in require_params: + if param not in current_params: + missing_param_list.append(param) + return missing_param_list + +def generate_key(): + key_obj = StringIO() + key = RSAKey.generate(2048) + key.write_private_key(key_obj) + return key_obj.getvalue(), 'ssh-rsa ' + key.get_base64() + + +class HTTP: + @classmethod + def request(cls, method: str, url: str, token, data: dict, **kwargs): + status, result = 0, '' + headers = { + 'Authorization': token, + 'Content-Type': 'application/json' + } + method = method.upper() + methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] + if method not in methods: + raise Exception('请求方式不存在!') + data = json.dumps(data) + for _ in range(3): + try: + response = requests.request(method=method, url=url, json=None, headers=headers, data=data, **kwargs) + if response.status_code != 200: + status = response.status_code + data = response.json() + result = data['message'] + break + else: + resp = response.json() + status, result = response.status_code, resp['data'] + break + except requests.exceptions.ConnectTimeout as e: + logger.info('Request Timeout, retry...') + status = 400 + result = '请求超时, 重试三次' + + return status, result + + +class JWT: + @staticmethod + def _encode(payload: dict, exp: int=60 * 5): + """ + 生成JWT Token + :args + payload 载体 + exp 过期时间 (单位秒) 默认时间5分钟 + """ + payload['exp'] = time.time() + exp + # 默认不可逆加密算法为HS256 + return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") -- Gitee