#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2019-09-17 08:58
# @Author  : Jieay
# @File    : commapi.py

import os
import re
import sys
import json
import copy
import time
import base64
import string
import random
import uuid
import fcntl
import inspect
import datetime
import functools
import subprocess


from rest_framework.exceptions import APIException


def check_legal_python():
    """检查合法python版本, 3.6 <= python < 3.9"""
    version_major = sys.version_info.major
    version_minor = sys.version_info.minor
    if version_major > 2 and 6 <= version_minor < 9:
        return True
    return False


def func_execution_time(func):
    """
    方法执行时间 装饰器
    :param func:
    :return:
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print('{} took execution time {} ms'.format(func.__name__, (end - start) * 1000))
        return res
    return wrapper


def get_random(mun, ty=None):
    """
    获取随机唯一字符串
    :param mun: 随机字符长度
    :param ty: 头部前缀字符
    :return:
    """
    if ty == 'tyint':
        restr = string.digits
    elif ty == 'tystr':
        restr = string.ascii_letters
    else:
        restr = string.digits + string.ascii_letters

    str_list = []
    for i in range(int(mun)):
        cs = random.choice(restr)
        str_list.append(cs)
    return ''.join(str_list)


def get_unique_id(title=None):
    """
    :param title: unique_id 的头部， 默认无
    :return: str 默认为28位数字
    """
    today_time = datetime.datetime.now()
    date_time = today_time.strftime("%Y%m%d%H%M%S")
    time_stamp = today_time.strftime("%s")
    time_str = str(date_time) + str(time_stamp)
    random_mun = get_random(4, ty='tyint')
    if title:
        str_id = '{0}{1}{2}'.format(str(title), time_str, random_mun)
    else:
        str_id = '{0}{1}'.format(time_str, random_mun)
    return str_id


def str_to_float(data):
    """
    将字符串转化成浮点数
    """
    try:
        data = float(data)
    except:
        data = data
    return data


def datetime_to_str(o):
    """
    封装将 datetime 时间对象转换字符串格式
    :param o: 时间对象：datetime.datetime or datetime.date
    :return:
    """
    if isinstance(o, datetime.datetime):
        return o.strftime('%Y-%m-%d %H:%M:%S')
    elif isinstance(o, datetime.date):
        return o.strftime('%Y-%m-%d')
    else:
        return str(o)


def get_time_stamp(datetime_obj):
    """
    获取时间戳
    :param datetime_obj: 时间对象
    :return:
    """
    if isinstance(datetime_obj, datetime.datetime):
        return int(time.mktime(datetime_obj.timetuple()))
    elif re.match(r'\d+-\d+-\d+ \d+:\d+:\d+', datetime_obj):
        time_strp = time.strptime(datetime_obj, '%Y-%m-%d %H:%M:%S')
        return int(time.mktime(time_strp))
    return


def check_json_format(raw_msg):
    """
    用于判断一个字符串是否符合Json格式
    :param raw_msg: 源字符
    :return: 布尔值
    """
    if raw_msg == 'null':
        return False

    if isinstance(raw_msg, str):
        try:
            if check_legal_python():
                json.loads(raw_msg, encoding='utf-8')
            else:
                json.loads(raw_msg)
        except ValueError:
            return False
        return True
    else:
        return False


def get_serializer_data_to_json(serializer_data):
    data = copy.deepcopy(serializer_data)
    if isinstance(data, dict):
        for k, v in data.items():
            if check_json_format(v):
                serializer_data[k] = json.loads(v)
            if isinstance(v, dict):
                for kk, vv in v.items():
                    if check_json_format(vv):
                        serializer_data[k][kk] = json.loads(vv)

    if isinstance(data, list):
        for nu, me in enumerate(data):
            if isinstance(me, dict):
                for k, v in me.items():
                    if check_json_format(v):
                        serializer_data[nu][k] = json.loads(v)
                    if isinstance(v, dict):
                        for kk, vv in v.items():
                            if check_json_format(vv):
                                serializer_data[nu][k][kk] = json.loads(vv)
    return serializer_data


class ApiLimitException(APIException):
    status_code = 503
    default_detail = 'Task already exists, try again later.'


class ApiFieldCheckException(APIException):
    status_code = 507
    default_detail = 'Field check exception.'


def api_limit_from_db(model, ck_field, key=None):
    """
    通过查询数据库指定字段条件，限制API接口请求
    :param model: 数据库model
    :param ck_field: 获取 request.data 查询的字段名称
    :param key: 自定义字段筛选条件，类型为 dict
    """
    def decorate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            assert isinstance(key, dict), ApiLimitException(
                detail="@api_limit_from_db() missing required argument: 'key'"
            )

            request_data = args[1].data
            db_key = {}
            if isinstance(ck_field, (list, tuple)):
                ck_field_list = ck_field
            else:
                ck_field_list = [ck_field]
            for i in ck_field_list:
                db_field = request_data.get(i)
                if db_field is not None:
                    db_key[i] = db_field
            assert db_key, ApiLimitException(
                detail="api_limit_from_db() missing required data: 'ck_field'"
            )

            if isinstance(key, dict):
                key.update(db_key)
                obj = model.objects.filter(**key)
                for ok, ov in db_key.items():
                    key.pop(ok)
                if obj:
                    the_object = True
                else:
                    the_object = False
            else:
                the_object = False
            assert not the_object, ApiLimitException(
                detail="@api_limit_from_db() check db task already exists."
            )

            res = func(*args, **kwargs)
            return res
        return wrapper
    return decorate


def check_request_field(data, ck_field):
    """
    检查请求中是否存在指定的字段
    :param data: 请求数据
    :param ck_field: 指定的字段，可以是多个
    """
    if not isinstance(data, dict):
        raise ApiFieldCheckException(detail='The request is not JSON.')
    field_list = []
    if isinstance(ck_field, (list, tuple)):
        field_list.extend([x for x in ck_field])
    else:
        field_list.append(ck_field)

    ck_field_list = copy.deepcopy(field_list)
    for i in ck_field_list:
        ck_data_field = data.get(i, None)
        if ck_data_field is not None:
            field_list.remove(i)
    if field_list:
        msg_filed = ','.join(field_list)
        raise ApiFieldCheckException(detail='The request data {} is not exist.'.format(msg_filed))

    return data


def check_model_field(data, model, **kwargs):
    """
    检查请求的数据，指定字段是否存在数据库中，is_exist: 默认不存在则报异常，否则存在报异常
    :param data: 请求数据
    :param model: 数据库 model
    :param kwargs: ck_field 指定检查的字段，可以是多个
    """
    field_list = []
    exclude_fields = ['ck_field', 'is_exist', 'check_del']
    ck_field = kwargs.get('ck_field', None)
    if isinstance(ck_field, (list, tuple)):
        field_list.extend(ck_field)
    if ck_field:
        field_list.append(ck_field)
    if isinstance(kwargs, dict):
        _ks = list(kwargs.keys())
        for ef in exclude_fields:
            if ef in _ks:
                _ks.remove(ef)
        field_list.extend(_ks)
    check_request_field(data=data, ck_field=field_list)

    ck_dict = {}
    for i in field_list:
        fd = data.get(i, None)
        if fd:
            ck_dict[i] = fd

    is_exist = kwargs.get('is_exist', False)
    check_del = kwargs.get('check_del', None)
    if check_del:
        ck_dict['soft_del'] = False

    ck_obj = model.objects.filter(**ck_dict)
    if is_exist is True:
        if ck_obj.exists():
            msg_filed = ', '.join(ck_dict)
            raise ApiFieldCheckException(detail='The request data {} is exist for model.'.format(msg_filed))
    else:
        if not ck_obj.exists():
            msg_filed = ', '.join(ck_dict)
            raise ApiFieldCheckException(detail='The request data {} is not exist for model.'.format(msg_filed))

    return data


def clean_response_field(data, ck_field):
    """
    删除请求返回值中的字段
    :param data: 返回数据
    :param ck_field: 需要删除的字段
    """
    data_cp = copy.deepcopy(data)

    if not isinstance(data, (dict, list)):
        raise ApiFieldCheckException(detail='The response is not JSON.')
    field_list = []
    if isinstance(ck_field, (list, tuple)):
        field_list.extend([x for x in ck_field])
    else:
        field_list.append(ck_field)

    if isinstance(data, dict):
        for k, v in data_cp.items():
            if k in field_list:
                data.pop(k)
    if isinstance(data, list):
        for m, obj in enumerate(data_cp):
            if isinstance(obj, dict):
                for kk, vv in obj.items():
                    if kk in field_list:
                        data[m].pop(kk)
    return data


def get_model_field_data(model, ck_field, *args, **kwargs):
    """
    获取指定字段的数据，并且可以指定字段排序
    :param model: 数据库 model
    :param ck_field: 自定字段，单个或者一个列表
    :param args: 指定字段，多个字段
    :param kwargs: 排序字段order_by， 正序'xxx'|倒序'-xxx'
    """
    filed_list = []
    if isinstance(ck_field, (list, tuple)):
        filed_list.extend(ck_field)
    else:
        filed_list.append(ck_field)
    for i in args:
        if isinstance(i, (list, tuple)):
            filed_list.extend(i)
        else:
            filed_list.append(i)
    order_by = kwargs.get('order_by')
    if order_by is not None:
        ck_obj = model.objects.all().order_by(order_by).values(*filed_list)
    else:
        ck_obj = model.objects.all().values(*filed_list)
    return list(ck_obj)


def check_dict_fields(data, ck_fields):
    """
    检查数据字典中，查询的字段不存在值则 返回 False
    :param data: 数据字典
    :param ck_fields: 查询字段 列表
    :return: False or True
    """
    if not isinstance(ck_fields, (list, tuple)):
        ck_fields = [ck_fields]

    _info = copy.deepcopy(ck_fields)
    if isinstance(data, dict):
        for i in ck_fields:
            ck_v = data.get(i, None)
            if ck_v is not None:
                _info.remove(i)
    if _info:
        return False
    return True


def str_to_int(data):
    """
    将字符串转化成数字
    :param data: str
    :return: int
    """
    try:
        data_info = int(data)
    except:
        data_info = data
    return data_info


def get_str_to_class(data):
    """
    解析对象字符串，将字符串转化成类名称和包的路径
    :param data: 对象字符串
    :return: (类名称, 包路径)
    """
    # data = "<class 'app.models.asset.envs_model.EnvsModel'>"
    data = data.replace('<', '').replace('>', '').replace("'", "").split(' ')
    if len(data) == 2:
        path_str = None
        m_name = None
        if data[0] == 'class':
            p_k = data[1].split('.')
            path_str = '.'.join(p_k[:-1])
            m_name = p_k[-1]
        return m_name, path_str
    return


def get_model_data_from_id(_id, model):
    """
    通过 ID 获取数据库中该条记录的数据
    :param _id: ID
    :param model: 表对象
    :return: `dict`
    """
    obj = model.objects.filter(id=_id).values()
    return obj[0]


def set_mark_delete(_id, model, **kwargs):
    """
    数据软删除，更新数据的标记记录
    :param _id: ID
    :param model: model
    :param kwargs: soft_del=True
    """
    if not kwargs:
        kwargs['soft_del'] = True
    obj = model.objects.filter(id=_id).update(**kwargs)
    return obj


def set_base_response_data(data=None):
    """
    处理自定义接口返回值的 data 数据中的状态字段
    """
    _data = {}
    if isinstance(data, dict):
        _data = copy.deepcopy(data)
        for i in ['status', 'code', 'msg']:
            if data.get(i, None) is not None:
                _data.pop(i)
    else:
        if data is not None:
            _data = data
    return _data


def check_telephone(telephone):
    """
    检查手机号码的合法性
    :param telephone: 手机号字符
    """
    regx = re.match('^[1-9][1-9][0-9]{9}$', telephone)
    if regx is None:
        return False
    return True


def modify_dict_mapping_dict(data, mapping_dict):
    """
    实现将一个字典的key映射成另外的key
    :param data: 要进行处理的数据字典，旧数据字典
    :param mapping_dict: 映射表，字典类型
    :return: 进过修改key名字的新数据字典
    """
    new_data = {}
    # 判断是否是JSON格式
    if check_json_format(data):
        data = json.loads(data)

    # 判断传入的参数是否为dict类型
    if not isinstance(data, dict):
        return new_data
    if not isinstance(mapping_dict, dict):
        return new_data
    for d_k, d_v in data.items():
        if check_json_format(d_v):
            d_v = json.loads(d_v)
        n_d_v = copy.deepcopy(d_v)
        # if isinstance(d_v, list):
        #     if d_v and isinstance(d_v[0], dict):
        #         n_d_v = d_v[0].get('name')

        if isinstance(n_d_v, (list, dict, tuple)):
            n_d_v = json.dumps(n_d_v)

        new_ks = mapping_dict.get(d_k)
        if isinstance(new_ks, list):
            for kk in new_ks:
                new_data[kk] = n_d_v
        else:
            if new_ks:
                new_data[new_ks] = n_d_v
    return new_data


def remove_fields_not_exists_model(model, dict_data):
    """
    删除不存在model字段中的dict keys
    :param model: 数据库模型
    :param dict_data: 字典对象
    :return:
    """
    field_names = [f.name for f in model._meta.fields]
    temp_dict = copy.copy(dict_data)
    for key in temp_dict.keys():
        if key not in field_names:
            dict_data.pop(key)


def order_time_mg(location_datetime, time_type=None, leadtime=10, timeout=10):
    """
    订单超时或者提前时间管理
    :param location_datetime: 指定时间
    :param time_type: 时间类型，超时或者提前，[ leadtime | timeout ]
    :param leadtime: 提前几分钟， 默认提前10分钟
    :param timeout: 超时几分钟，默认10分钟
    :return: bool
    """
    ck_time = False
    location_stamp = get_time_stamp(location_datetime)

    if time_type == 'timeout':
        # 超时
        timeout_limit_datetime = datetime.datetime.now() - datetime.timedelta(minutes=timeout)
        timeout_limit_stamp = get_time_stamp(timeout_limit_datetime)
        if timeout_limit_stamp > location_stamp:
            ck_time = True
    elif time_type == 'leadtime':
        # 提前
        leadtime_limit_datetime = datetime.datetime.now() + datetime.timedelta(minutes=leadtime)
        leadtime_limit_stamp = get_time_stamp(leadtime_limit_datetime)
        if leadtime_limit_stamp > location_stamp:
            ck_time = True

    return ck_time


def get_function_name(c_name=None):
    """
    动态获取正在运行函数(或方法)名称
    :param c_name: 类名称
    """
    if c_name is not None:
        f_name = '%s.%s' % (c_name, inspect.stack()[1][3])
    else:
        f_name = '%s' % inspect.stack()[1][3]
    return f_name


def func_exe_retry(ExceptionToCheck, tries=4, delay=3, back_off=2, logger=None):
    """
    函数运行重试装饰器
    :param ExceptionToCheck: 异常参数元组（TypeError,ValueError）
    :param tries: 重试次数
    :param delay: 重试延时时间 （秒）
    :param back_off: 重试延时倍数
    :param logger: 日志参数
    """
    def deco_retry(f):
        @functools.wraps(f)
        def f_retry(*args, **kwargs):
            m_tries, m_delay = tries, delay
            while m_tries > 1:
                try:
                    return f(*args, **kwargs)
                except ExceptionToCheck as e:
                    msg = "%s, Retrying in %d seconds..." % (str(e), m_delay)
                    if logger:
                        logger.warning(msg)
                    else:
                        print(msg)
                    time.sleep(m_delay)
                    m_tries -= 1
                    m_delay *= back_off
            return f(*args, **kwargs)
        return f_retry  # true decorator
    return deco_retry


def random_sleep_ms():
    """随机毫秒休眠"""
    sleep_time = random.randint(0, 100) * 0.01
    time.sleep(sleep_time)
    return sleep_time


def filter_attrs(attrs):
    for k, v in attrs.items():
        if k.startswith("__"):
            yield k, v
        else:
            if isinstance(v, tuple):
                yield k, v[0]
                yield f'{k}_NAME', v[1]
            else:
                yield k, v


class ChoicesMeta(type):
    def __new__(cls, name, bases, attrs):
        real_attrs = dict(filter_attrs(attrs))
        cls = super(ChoicesMeta, cls).__new__(cls, name, bases, real_attrs)
        cls._dict = {v[0]: v[1] for k, v in attrs.items() if not k.startswith("__") and isinstance(v, tuple)}
        cls._items = list(cls._dict.items())
        cls._values = list(map(lambda x: x[1], cls._items))
        cls._keys = list(map(lambda x: x[0], cls._items))
        return cls

    def __contains__(self, val):
        return val in self.keys()

    def __getitem__(self, k):
        return self._dict[k]

    def keys(cls):
        return cls._keys

    def values(self):
        return self._values

    def items(self):
        return self._items

    def dict(self):
        return self._dict

    def get(self, key, default=None):
        return self._dict.get(key, default)

    def choices(self):
        return self.items()


class Choices(object, metaclass=ChoicesMeta):
    """ 自定义选择常量构造的用法
        In [18]: class FeedType(Choices):
            ...:     IMAGE = (1, '⽂文字图⽚片')
            ...:     VIDEO = (2, '⽂文字视频')
            ...:     ARTICLE = (3, '⽂文章')
            ...:

        In [19]: FeedType.IMAGE == 1
        Out[19]: True

        In [20]: FeedType.IMAGE_NAME == '文字图片'
        Out[20]: False

        In [21]: FeedType.IMAGE in FeedType
        Out[21]: True

        In [22]: FeedType.keys()
        Out[22]: [1, 2, 3]

        In [23]: FeedType.values()
        Out[23]: ['⽂文字图⽚片', '⽂文字视频', '⽂文章']

        In [24]: FeedType.items()
        Out[24]: [(1, '⽂文字图⽚片'), (2, '⽂文字视频'), (3, '⽂文章')]

        In [25]: FeedType.dict()
        Out[25]: {1: '⽂文字图⽚片', 2: '⽂文字视频', 3: '⽂文章'}

        In [26]: FeedType.choices()
        Out[26]: [(1, '⽂文字图⽚片'), (2, '⽂文字视频'), (3, '⽂文章')]

        In [27]: FeedType.get(FeedType.IMAGE)
        Out[27]: '⽂文字图⽚片'
    """


class ModelChoicesMeta(type):
    """数据库模型选择常量元类"""
    def __new__(cls, name, bases, data):
        cls = super(ModelChoicesMeta, cls).__new__(cls, name, bases, data)
        model_name, fields = data.get('data', [None, []])
        data_dict = []
        if model_name:
            try:
                ck_obj = model_name.objects.all().values_list(*fields)
                if ck_obj:
                    data_dict = list(ck_obj)
            except Exception as e:
                print(e)
        cls._dict = dict(data_dict)
        cls._items = list(cls._dict.items())
        cls._values = cls._dict.values()
        cls._keys = cls._dict.keys()
        return cls

    def __contains__(self, val):
        return val in self.keys()

    def __getitem__(self, k):
        return self._dict[k]

    def keys(cls):
        return cls._keys

    def values(self):
        return self._values

    def items(self):
        return self._items

    def dict(self):
        return self._dict

    def get(self, key, default=None):
        return self._dict.get(key, default)

    def choices(self):
        return self.items()


class ModelChoices(object, metaclass=ModelChoicesMeta):
    """
    使用数据库模型，指定字段，构造自定义选择常量的用法
    In [2]: class OAWorkFlowTypeChoices(ModelChoices):
       ...:
       ...:     data = (OAWorkFlowTypesModel, ('number', 'name'))
       ...:

    In [3]: OAWorkFlowTypeChoices.choices()
    Out[3]: [(1, 'KJ008'), (2, 'KJ021')]

    In [4]: OAWorkFlowTypeChoices.get(1)
    Out[4]: 'KJ008'

    In [5]: OAWorkFlowTypeChoices.get(2)
    Out[5]: 'KJ021'

    In [6]: OAWorkFlowTypeChoices.keys()
    Out[6]: dict_keys([1, 2])

    In [7]: OAWorkFlowTypeChoices.values()
    Out[7]: dict_values(['KJ008', 'KJ021'])

    In [8]: OAWorkFlowTypeChoices.items()
    Out[8]: [(1, 'KJ008'), (2, 'KJ021')]

    In [9]: OAWorkFlowTypeChoices.dict()
    Out[9]: {1: 'KJ008', 2: 'KJ021'}

    """


def set_custom_transaction_lock_data(new_data, old_data):
    """
    构造自定义事务锁数据更新
    :param new_data: `dict` 新数据库数据，数据库字段的对象数据需要反序列化，json.loads
    :param old_data: `dict` 旧的数据
    """
    # 防止数据调用被更新，做深度拷贝
    data = copy.deepcopy(new_data)
    _old_data = copy.deepcopy(old_data)
    # 遍历旧的数据，取出字段和字段值
    for o_k, o_v in _old_data.items():
        new_v = new_data.get(o_k)
        # 判断新的字段值是否为序列化对象
        if check_json_format(new_v):
            new_v = json.loads(new_v)
        # 判断旧的字段值是否为序列化对象
        if check_json_format(o_v):
            o_v = json.loads(o_v)

        # 新字段值是否为空
        if new_v:
            # 将新字段值，做深度拷贝
            _new_v = copy.deepcopy(new_v)
            if isinstance(new_v, list) and isinstance(o_v, list):
                new_vv = _new_v
                for ov in o_v:
                    if ov not in new_vv:
                        new_vv.append(ov)
                data[o_k] = json.dumps(new_vv)

            elif isinstance(new_v, dict) and isinstance(o_v, dict):
                new_vv = _new_v.update(o_v)
                data[o_k] = json.dumps(new_vv)

            elif new_v and o_v:
                if isinstance(o_v, (list, dict)):
                    o_v = json.dumps(o_v)
                data[o_k] = o_v
            else:
                pass
        else:
            if isinstance(o_v, (list, dict)):
                o_v = json.dumps(o_v)
            data[o_k] = o_v
    return data


def get_command_exec_result(command_str):
    """
    执行shell命令，且返回执行结果
    :param command_str: shell 命令字符串
    :return: (0, stdout)
    """
    return subprocess.getstatusoutput(command_str)


def check_and_mkdir_file_path_dir(file_path, is_dir=False):
    """
    检查传入文件的目录是否存在， 没有则创建
    :param file_path: 文件绝对路径
    :param is_dir: 传入路径是否只包含目录路径
    :return:
    """
    if not is_dir:
        file_dir_name = os.path.dirname(file_path)
    else:
        file_dir_name = file_path
    try:
        if not os.path.exists(file_dir_name):
            os.makedirs(file_dir_name)
        return True
    except Exception as e:
        return False


def write_text_file_add_lock(data, file_path, write_type="w"):
    """
    编写文本文件(加等待锁， 防止写入冲突)
    :param data: str
    :param file_path: 写入文件绝对路径, str
    :param write_type: 写类型, str, w或a, 即覆盖或追加
    :return:
    """
    try:
        check_and_mkdir_file_path_dir(file_path=file_path)
        with open(file_path, write_type) as f:
            fcntl.flock(f, fcntl.LOCK_EX)
            f.write(data)
            fcntl.flock(f, fcntl.LOCK_UN)
        return True
    except Exception as e:
        return False


def get_a_uuid():
    """
    获取一个UUID
    :return: str
    """
    uuid_obj = uuid.uuid4()
    uuid_str = str(uuid_obj)
    return uuid_str


def get_cur_host_mac_addr():
    """
    获取当前主机节点MAC地址
    :return:
    """
    addr = hex(uuid.getnode())[2:].upper()
    standard_addr = '0' * (12 - len(addr)) + addr
    standard_mac_addr = '-'.join(standard_addr[i:i + 2] for i in range(0, len(standard_addr), 2))
    return standard_mac_addr


def is_ip(ip_str):
    """检查字符串是否是IP"""
    ip_str = str(ip_str)
    p = re.compile(r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
    if p.match(ip_str):
        return True
    else:
        return False


def datetime_str_to_str(dt_str):
    """
    将指定时间字符串转化标准格式
    Args:
        dt_str: 2024-08-18T19:59:00.194116

    Returns: `str` 2024-08-18 19:59:00
    """
    datetime_obj = datetime.datetime.strptime(dt_str, '%Y-%m-%dT%H:%M:%S.%f')
    return datetime_obj.strftime('%Y-%m-%d %H:%M:%S')


def bytes_to_str(data):
    """
    将 bytes 类型转化成字符串
    Args:
        data: 数据

    Returns: `str`
    """
    if isinstance(data, bytes):
        return data.decode(encoding='utf-8')
    return data


def str_to_bytes(data):
    """
    将字符串转化成 bytes 类型
    Args:
        data: 数据

    Returns: `bytes`
    """
    if isinstance(data, str):
        return data.encode(encoding='utf-8')
    return data


def get_base64_encoded(data, out_str=True):
    """
    获取 Base64 编码
    Args:
        data: `str`|`bytes` 需要编码字符
        out_str: `bool` 是否输出字符串

    Returns: `str`|`bytes`
    """
    if isinstance(data, bytes):
        _data = data
    else:
        # 将字符串编码为字节
        _data = data.encode('utf-8')
    # 编码为 Base64
    encoded_data = base64.b64encode(_data)
    if out_str is True:
        return encoded_data.decode('utf-8')
    return encoded_data


def get_base64_decode(encoded_data, out_str=True):
    """
    获取 Base64 解码
    Args:
        encoded_data: `str`|`bytes` Base64编码字符
        out_str: `bool` 是否输出字符串

    Returns: `str`|`bytes`
    """
    if isinstance(encoded_data, bytes):
        data = encoded_data
    else:
        # 将字符串编码为字节
        data = encoded_data.encode('utf-8')
    # 解码 Base64 数据
    decoded_data = base64.b64decode(data)
    if out_str is True:
        return decoded_data.decode('utf-8')
    return decoded_data
